First Commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
/** Handling of SQL arrays.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/array.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,103 @@
|
||||
/* Handling of SQL arrays.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/field 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_ARRAY
|
||||
#define PQXX_H_ARRAY
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "pqxx/internal/encoding_group.hxx"
|
||||
#include "pqxx/internal/encodings.hxx"
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Low-level array parser.
|
||||
/** Use this to read an array field retrieved from the database.
|
||||
*
|
||||
* This parser will only work reliably if your client encoding is UTF-8, ASCII,
|
||||
* or a single-byte encoding which is a superset of ASCII (such as Latin-1).
|
||||
*
|
||||
* Also, the parser only supports array element types which use either a comma
|
||||
* or a semicolon ("," or ";") as the separator between array elements. All
|
||||
* built-in types use comma, except for one which uses semicolon, but some
|
||||
* custom types may not work.
|
||||
*
|
||||
* The input is a C-style string containing the textual representation of an
|
||||
* array, as returned by the database. The parser reads this representation
|
||||
* on the fly. The string must remain in memory until parsing is done.
|
||||
*
|
||||
* Parse the array by making calls to @ref get_next until it returns a
|
||||
* @ref juncture of "done". The @ref juncture tells you what the parser found
|
||||
* in that step: did the array "nest" to a deeper level, or "un-nest" back up?
|
||||
*/
|
||||
class PQXX_LIBEXPORT array_parser
|
||||
{
|
||||
public:
|
||||
/// What's the latest thing found in the array?
|
||||
enum class juncture
|
||||
{
|
||||
/// Starting a new row.
|
||||
row_start,
|
||||
/// Ending the current row.
|
||||
row_end,
|
||||
/// Found a NULL value.
|
||||
null_value,
|
||||
/// Found a string value.
|
||||
string_value,
|
||||
/// Parsing has completed.
|
||||
done,
|
||||
};
|
||||
|
||||
// TODO: constexpr noexcept. Breaks ABI.
|
||||
/// Constructor. You don't need this; use @ref field::as_array instead.
|
||||
/** The parser only remains valid while the data underlying the @ref result
|
||||
* remains valid. Once all `result` objects referring to that data have been
|
||||
* destroyed, the parser will no longer refer to valid memory.
|
||||
*/
|
||||
explicit array_parser(
|
||||
std::string_view input,
|
||||
internal::encoding_group = internal::encoding_group::MONOBYTE);
|
||||
|
||||
/// Parse the next step in the array.
|
||||
/** Returns what it found. If the juncture is @ref juncture::string_value,
|
||||
* the string will contain the value. Otherwise, it will be empty.
|
||||
*
|
||||
* Call this until the @ref array_parser::juncture it returns is
|
||||
* @ref juncture::done.
|
||||
*/
|
||||
std::pair<juncture, std::string> get_next();
|
||||
|
||||
private:
|
||||
std::string_view m_input;
|
||||
internal::glyph_scanner_func *const m_scan;
|
||||
|
||||
/// Current parsing position in the input.
|
||||
std::string::size_type m_pos = 0u;
|
||||
|
||||
std::string::size_type scan_single_quoted_string() const;
|
||||
std::string parse_single_quoted_string(std::string::size_type end) const;
|
||||
std::string::size_type scan_double_quoted_string() const;
|
||||
std::string parse_double_quoted_string(std::string::size_type end) const;
|
||||
std::string::size_type scan_unquoted_string() const;
|
||||
std::string parse_unquoted_string(std::string::size_type end) const;
|
||||
|
||||
std::string::size_type scan_glyph(std::string::size_type pos) const;
|
||||
std::string::size_type
|
||||
scan_glyph(std::string::size_type pos, std::string::size_type end) const;
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
/** BYTEA (binary string) conversions.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/binarystring.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,236 @@
|
||||
/* Deprecated representation for raw, binary data.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/binarystring 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_BINARYSTRING
|
||||
#define PQXX_H_BINARYSTRING
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "pqxx/result.hxx"
|
||||
#include "pqxx/strconv.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
class binarystring;
|
||||
template<> struct string_traits<binarystring>;
|
||||
|
||||
|
||||
/// Binary data corresponding to PostgreSQL's "BYTEA" binary-string type.
|
||||
/** @ingroup escaping-functions
|
||||
* @deprecated Use @c std::basic_string<std::byte> and
|
||||
* @c std::basic_string_view<std::byte> for binary data. In C++20 or better,
|
||||
* any @c contiguous_range of @c std::byte will do.
|
||||
*
|
||||
* This class represents a binary string as stored in a field of type @c bytea.
|
||||
*
|
||||
* Internally a binarystring is zero-terminated, but it may also contain null
|
||||
* bytes, they're just like any other byte value. So don't assume that it's
|
||||
* safe to treat the contents as a C-style string.
|
||||
*
|
||||
* The binarystring retains its value even if the result it was obtained from
|
||||
* is destroyed, but it cannot be copied or assigned.
|
||||
*
|
||||
* \relatesalso transaction_base::quote_raw
|
||||
*
|
||||
* To include a @c binarystring value in an SQL query, escape and quote it
|
||||
* using the transaction's @c quote_raw function.
|
||||
*
|
||||
* @warning This class is implemented as a reference-counting smart pointer.
|
||||
* Copying, swapping, and destroying binarystring objects that refer to the
|
||||
* same underlying data block is <em>not thread-safe</em>. If you wish to pass
|
||||
* binarystrings around between threads, make sure that each of these
|
||||
* operations is protected against concurrency with similar operations on the
|
||||
* same object, or other objects pointing to the same data block.
|
||||
*/
|
||||
class PQXX_LIBEXPORT binarystring
|
||||
{
|
||||
public:
|
||||
using char_type = unsigned char;
|
||||
using value_type = std::char_traits<char_type>::char_type;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = long;
|
||||
using const_reference = value_type const &;
|
||||
using const_pointer = value_type const *;
|
||||
using const_iterator = const_pointer;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
[[deprecated("Use std::byte for binary data.")]] binarystring(
|
||||
binarystring const &) = default;
|
||||
|
||||
/// Read and unescape bytea field.
|
||||
/** The field will be zero-terminated, even if the original bytea field
|
||||
* isn't.
|
||||
* @param F the field to read; must be a bytea field
|
||||
*/
|
||||
[[deprecated("Use std::byte for binary data.")]] explicit binarystring(
|
||||
field const &);
|
||||
|
||||
/// Copy binary data from std::string_view on binary data.
|
||||
/** This is inefficient in that it copies the data to a buffer allocated on
|
||||
* the heap.
|
||||
*/
|
||||
[[deprecated("Use std::byte for binary data.")]] explicit binarystring(
|
||||
std::string_view);
|
||||
|
||||
/// Copy binary data of given length straight out of memory.
|
||||
[[deprecated("Use std::byte for binary data.")]] binarystring(
|
||||
void const *, std::size_t);
|
||||
|
||||
/// Efficiently wrap a buffer of binary data in a @c binarystring.
|
||||
[[deprecated("Use std::byte for binary data.")]] binarystring(
|
||||
std::shared_ptr<value_type> ptr, size_type size) :
|
||||
m_buf{std::move(ptr)}, m_size{size}
|
||||
{}
|
||||
|
||||
/// Size of converted string in bytes.
|
||||
[[nodiscard]] size_type size() const noexcept { return m_size; }
|
||||
/// Size of converted string in bytes.
|
||||
[[nodiscard]] size_type length() const noexcept { return size(); }
|
||||
[[nodiscard]] bool empty() const noexcept { return size() == 0; }
|
||||
|
||||
[[nodiscard]] const_iterator begin() const noexcept { return data(); }
|
||||
[[nodiscard]] const_iterator cbegin() const noexcept { return begin(); }
|
||||
[[nodiscard]] const_iterator end() const noexcept { return data() + m_size; }
|
||||
[[nodiscard]] const_iterator cend() const noexcept { return end(); }
|
||||
|
||||
[[nodiscard]] const_reference front() const noexcept { return *begin(); }
|
||||
[[nodiscard]] const_reference back() const noexcept
|
||||
{
|
||||
return *(data() + m_size - 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] const_reverse_iterator rbegin() const
|
||||
{
|
||||
return const_reverse_iterator{end()};
|
||||
}
|
||||
[[nodiscard]] const_reverse_iterator crbegin() const { return rbegin(); }
|
||||
[[nodiscard]] const_reverse_iterator rend() const
|
||||
{
|
||||
return const_reverse_iterator{begin()};
|
||||
}
|
||||
[[nodiscard]] const_reverse_iterator crend() const { return rend(); }
|
||||
|
||||
/// Unescaped field contents.
|
||||
[[nodiscard]] value_type const *data() const noexcept { return m_buf.get(); }
|
||||
|
||||
[[nodiscard]] const_reference operator[](size_type i) const noexcept
|
||||
{
|
||||
return data()[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] PQXX_PURE bool operator==(binarystring const &) const noexcept;
|
||||
[[nodiscard]] bool operator!=(binarystring const &rhs) const noexcept
|
||||
{
|
||||
return not operator==(rhs);
|
||||
}
|
||||
|
||||
binarystring &operator=(binarystring const &);
|
||||
|
||||
/// Index contained string, checking for valid index.
|
||||
const_reference at(size_type) const;
|
||||
|
||||
/// Swap contents with other binarystring.
|
||||
void swap(binarystring &);
|
||||
|
||||
/// Raw character buffer (no terminating zero is added).
|
||||
/** @warning No terminating zero is added! If the binary data did not end in
|
||||
* a null character, you will not find one here.
|
||||
*/
|
||||
[[nodiscard]] char const *get() const noexcept
|
||||
{
|
||||
return reinterpret_cast<char const *>(m_buf.get());
|
||||
}
|
||||
|
||||
/// Read contents as a std::string_view.
|
||||
[[nodiscard]] std::string_view view() const noexcept
|
||||
{
|
||||
return std::string_view(get(), size());
|
||||
}
|
||||
|
||||
/// Read as regular C++ string (may include null characters).
|
||||
/** This creates and returns a new string object. Don't call this
|
||||
* repeatedly; retrieve your string once and keep it in a local variable.
|
||||
* Also, do not expect to be able to compare the string's address to that of
|
||||
* an earlier invocation.
|
||||
*/
|
||||
[[nodiscard]] std::string str() const;
|
||||
|
||||
/// Access data as a pointer to @c std::byte.
|
||||
[[nodiscard]] std::byte const *bytes() const
|
||||
{
|
||||
return reinterpret_cast<std::byte const *>(get());
|
||||
}
|
||||
|
||||
/// Read data as a @c std::basic_string_view<std::byte>.
|
||||
[[nodiscard]] std::basic_string_view<std::byte> bytes_view() const
|
||||
{
|
||||
return std::basic_string_view<std::byte>{bytes(), size()};
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<value_type> m_buf;
|
||||
size_type m_size{0};
|
||||
};
|
||||
|
||||
|
||||
template<> struct nullness<binarystring> : no_null<binarystring>
|
||||
{};
|
||||
|
||||
|
||||
/// String conversion traits for @c binarystring.
|
||||
/** Defines the conversions between a @c binarystring and its PostgreSQL
|
||||
* textual format, for communication with the database.
|
||||
*
|
||||
* These conversions rely on the "hex" format which was introduced in
|
||||
* PostgreSQL 9.0. Both your libpq and the server must be recent enough to
|
||||
* speak this format.
|
||||
*/
|
||||
template<> struct string_traits<binarystring>
|
||||
{
|
||||
static std::size_t size_buffer(binarystring const &value) noexcept
|
||||
{
|
||||
return internal::size_esc_bin(std::size(value));
|
||||
}
|
||||
|
||||
static zview to_buf(char *begin, char *end, binarystring const &value)
|
||||
{
|
||||
return generic_to_buf(begin, end, value);
|
||||
}
|
||||
|
||||
static char *into_buf(char *begin, char *end, binarystring const &value)
|
||||
{
|
||||
auto const budget{size_buffer(value)};
|
||||
if (internal::cmp_less(end - begin, budget))
|
||||
throw conversion_overrun{
|
||||
"Not enough buffer space to escape binary data."};
|
||||
std::string_view text{value.view()};
|
||||
internal::esc_bin(binary_cast(text), begin);
|
||||
return begin + budget;
|
||||
}
|
||||
|
||||
static binarystring from_string(std::string_view text)
|
||||
{
|
||||
auto const size{pqxx::internal::size_unesc_bin(std::size(text))};
|
||||
std::shared_ptr<unsigned char> buf{
|
||||
new unsigned char[size], [](unsigned char const *x) { delete[] x; }};
|
||||
pqxx::internal::unesc_bin(text, reinterpret_cast<std::byte *>(buf.get()));
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return binarystring{std::move(buf), size};
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
/** Binary Large Objects interface.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/blob.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,351 @@
|
||||
/* Binary Large Objects interface.
|
||||
*
|
||||
* Read or write large objects, stored in their own storage on the server.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/largeobject 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_BLOB
|
||||
#define PQXX_H_BLOB
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#if defined(PQXX_HAVE_PATH)
|
||||
# include <filesystem>
|
||||
#endif
|
||||
|
||||
#if defined(PQXX_HAVE_RANGES) && __has_include(<ranges>)
|
||||
# include <ranges>
|
||||
#endif
|
||||
|
||||
#if defined(PQXX_HAVE_SPAN) && __has_include(<span>)
|
||||
# include <span>
|
||||
#endif
|
||||
|
||||
#include "pqxx/dbtransaction.hxx"
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/** Binary large object.
|
||||
*
|
||||
* This is how you store data that may be too large for the `BYTEA` type.
|
||||
* Access operations are similar to those for a file: you can read, write,
|
||||
* query or set the current reading/writing position, and so on.
|
||||
*
|
||||
* These large objects live in their own storage on the server, indexed by an
|
||||
* integer object identifier ("oid").
|
||||
*
|
||||
* Two `blob` objects may refer to the same actual large object in the
|
||||
* database at the same time. Each will have its own reading/writing position,
|
||||
* but writes to the one will of course affect what the other sees.
|
||||
*/
|
||||
class PQXX_LIBEXPORT blob
|
||||
{
|
||||
public:
|
||||
/// Create a new, empty large object.
|
||||
/** You may optionally specify an oid for the new blob. If you do, then
|
||||
* the new object will have that oid -- or creation will fail if there
|
||||
* already is an object with that oid.
|
||||
*/
|
||||
[[nodiscard]] static oid create(dbtransaction &, oid = 0);
|
||||
|
||||
/// Delete a large object, or fail if it does not exist.
|
||||
static void remove(dbtransaction &, oid);
|
||||
|
||||
/// Open blob for reading. Any attempt to write to it will fail.
|
||||
[[nodiscard]] static blob open_r(dbtransaction &, oid);
|
||||
// Open blob for writing. Any attempt to read from it will fail.
|
||||
[[nodiscard]] static blob open_w(dbtransaction &, oid);
|
||||
// Open blob for reading and/or writing.
|
||||
[[nodiscard]] static blob open_rw(dbtransaction &, oid);
|
||||
|
||||
/// You can default-construct a blob, but it won't do anything useful.
|
||||
/** Most operations on a default-constructed blob will throw @ref
|
||||
* usage_error.
|
||||
*/
|
||||
blob() = default;
|
||||
|
||||
/// You can move a blob, but not copy it. The original becomes unusable.
|
||||
blob(blob &&);
|
||||
/// You can move a blob, but not copy it. The original becomes unusable.
|
||||
blob &operator=(blob &&);
|
||||
|
||||
blob(blob const &) = delete;
|
||||
blob &operator=(blob const &) = delete;
|
||||
~blob();
|
||||
|
||||
/// Maximum number of bytes that can be read or written at a time.
|
||||
/** The underlying protocol only supports reads and writes up to 2 GB
|
||||
* exclusive.
|
||||
*
|
||||
* If you need to read or write more data to or from a binary large object,
|
||||
* you'll have to break it up into chunks.
|
||||
*/
|
||||
static constexpr std::size_t chunk_limit = 0x7fffffff;
|
||||
|
||||
/// Read up to `size` bytes of the object into `buf`.
|
||||
/** Uses a buffer that you provide, resizing it as needed. If it suits you,
|
||||
* this lets you allocate the buffer once and then re-use it multiple times.
|
||||
*
|
||||
* Resizes `buf` as needed.
|
||||
*
|
||||
* @warning The underlying protocol only supports reads up to 2GB at a time.
|
||||
* If you need to read more, try making repeated calls to @ref append_to_buf.
|
||||
*/
|
||||
std::size_t read(std::basic_string<std::byte> &buf, std::size_t size);
|
||||
|
||||
#if defined(PQXX_HAVE_SPAN)
|
||||
/// Read up to `std::size(buf)` bytes from the object.
|
||||
/** Retrieves bytes from the blob, at the current position, until `buf` is
|
||||
* full or there are no more bytes to read, whichever comes first.
|
||||
*
|
||||
* Returns the filled portion of `buf`. This may be empty.
|
||||
*/
|
||||
template<std::size_t extent = std::dynamic_extent>
|
||||
std::span<std::byte> read(std::span<std::byte, extent> buf)
|
||||
{
|
||||
return buf.subspan(0, raw_read(std::data(buf), std::size(buf)));
|
||||
}
|
||||
#endif // PQXX_HAVE_SPAN
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS) && defined(PQXX_HAVE_SPAN)
|
||||
/// Read up to `std::size(buf)` bytes from the object.
|
||||
/** Retrieves bytes from the blob, at the current position, until `buf` is
|
||||
* full or there are no more bytes to read, whichever comes first.
|
||||
*
|
||||
* Returns the filled portion of `buf`. This may be empty.
|
||||
*/
|
||||
template<binary DATA> std::span<std::byte> read(DATA &buf)
|
||||
{
|
||||
return {std::data(buf), raw_read(std::data(buf), std::size(buf))};
|
||||
}
|
||||
#else // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN
|
||||
/// Read up to `std::size(buf)` bytes from the object.
|
||||
/** @deprecated As libpqxx moves to C++20 as its baseline language version,
|
||||
* this will take and return `std::span<std::byte>`.
|
||||
*
|
||||
* Retrieves bytes from the blob, at the current position, until `buf` is
|
||||
* full (i.e. its current size is reached), or there are no more bytes to
|
||||
* read, whichever comes first.
|
||||
*
|
||||
* This function will not change either the size or the capacity of `buf`,
|
||||
* only its contents.
|
||||
*
|
||||
* Returns the filled portion of `buf`. This may be empty.
|
||||
*/
|
||||
template<typename ALLOC>
|
||||
std::basic_string_view<std::byte> read(std::vector<std::byte, ALLOC> &buf)
|
||||
{
|
||||
return {std::data(buf), raw_read(std::data(buf), std::size(buf))};
|
||||
}
|
||||
#endif // PQXX_HAVE_CONCEPTS && PQXX_HAVE_SPAN
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
/// Write `data` to large object, at the current position.
|
||||
/** If the writing position is at the end of the object, this will append
|
||||
* `data` to the object's contents and move the writing position so that
|
||||
* it's still at the end.
|
||||
*
|
||||
* If the writing position was not at the end, writing will overwrite the
|
||||
* prior data, but it will not remove data that follows the part where you
|
||||
* wrote your new data.
|
||||
*
|
||||
* @warning This is a big difference from writing to a file. You can
|
||||
* overwrite some data in a large object, but this does not truncate the
|
||||
* data that was already there. For example, if the object contained binary
|
||||
* data "abc", and you write "12" at the starting position, the object will
|
||||
* contain "12c".
|
||||
*
|
||||
* @warning The underlying protocol only supports writes up to 2 GB at a
|
||||
* time. If you need to write more, try making repeated calls to
|
||||
* @ref append_from_buf.
|
||||
*/
|
||||
template<binary DATA> void write(DATA const &data)
|
||||
{
|
||||
raw_write(std::data(data), std::size(data));
|
||||
}
|
||||
#else
|
||||
/// Write `data` large object, at the current position.
|
||||
/** If the writing position is at the end of the object, this will append
|
||||
* `data` to the object's contents and move the writing position so that
|
||||
* it's still at the end.
|
||||
*
|
||||
* If the writing position was not at the end, writing will overwrite the
|
||||
* prior data, but it will not remove data that follows the part where you
|
||||
* wrote your new data.
|
||||
*
|
||||
* @warning This is a big difference from writing to a file. You can
|
||||
* overwrite some data in a large object, but this does not truncate the
|
||||
* data that was already there. For example, if the object contained binary
|
||||
* data "abc", and you write "12" at the starting position, the object will
|
||||
* contain "12c".
|
||||
*
|
||||
* @warning The underlying protocol only supports writes up to 2 GB at a
|
||||
* time. If you need to write more, try making repeated calls to
|
||||
* @ref append_from_buf.
|
||||
*/
|
||||
template<typename DATA> void write(DATA const &data)
|
||||
{
|
||||
raw_write(std::data(data), std::size(data));
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Resize large object to `size` bytes.
|
||||
/** If the blob is more than `size` bytes long, this removes the end so as
|
||||
* to make the blob the desired length.
|
||||
*
|
||||
* If the blob is less than `size` bytes long, it adds enough zero bytes to
|
||||
* make it the desired length.
|
||||
*/
|
||||
void resize(std::int64_t size);
|
||||
|
||||
/// Return the current reading/writing position in the large object.
|
||||
[[nodiscard]] std::int64_t tell() const;
|
||||
|
||||
/// Set the current reading/writing position to an absolute offset.
|
||||
/** Returns the new file offset. */
|
||||
std::int64_t seek_abs(std::int64_t offset = 0);
|
||||
/// Move the current reading/writing position forwards by an offset.
|
||||
/** To move backwards, pass a negative offset.
|
||||
*
|
||||
* Returns the new file offset.
|
||||
*/
|
||||
std::int64_t seek_rel(std::int64_t offset = 0);
|
||||
/// Set the current position to an offset relative to the end of the blob.
|
||||
/** You'll probably want an offset of zero or less.
|
||||
*
|
||||
* Returns the new file offset.
|
||||
*/
|
||||
std::int64_t seek_end(std::int64_t offset = 0);
|
||||
|
||||
/// Create a binary large object containing given `data`.
|
||||
/** You may optionally specify an oid for the new object. If you do, and an
|
||||
* object with that oid already exists, creation will fail.
|
||||
*/
|
||||
static oid from_buf(
|
||||
dbtransaction &tx, std::basic_string_view<std::byte> data, oid id = 0);
|
||||
|
||||
/// Append `data` to binary large object.
|
||||
/** The underlying protocol only supports appending blocks up to 2 GB.
|
||||
*/
|
||||
static void append_from_buf(
|
||||
dbtransaction &tx, std::basic_string_view<std::byte> data, oid id);
|
||||
|
||||
/// Read client-side file and store it server-side as a binary large object.
|
||||
[[nodiscard]] static oid from_file(dbtransaction &, char const path[]);
|
||||
|
||||
#if defined(PQXX_HAVE_PATH) && !defined(_WIN32)
|
||||
/// Read client-side file and store it server-side as a binary large object.
|
||||
/** This overload is not available on Windows, where `std::filesystem::path`
|
||||
* converts to a `wchar_t` string rather than a `char` string.
|
||||
*/
|
||||
[[nodiscard]] static oid
|
||||
from_file(dbtransaction &tx, std::filesystem::path const &path)
|
||||
{
|
||||
return from_file(tx, path.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Read client-side file and store it server-side as a binary large object.
|
||||
/** In this version, you specify the binary large object's oid. If that oid
|
||||
* is already in use, the operation will fail.
|
||||
*/
|
||||
static oid from_file(dbtransaction &, char const path[], oid);
|
||||
|
||||
#if defined(PQXX_HAVE_PATH) && !defined(_WIN32)
|
||||
/// Read client-side file and store it server-side as a binary large object.
|
||||
/** In this version, you specify the binary large object's oid. If that oid
|
||||
* is already in use, the operation will fail.
|
||||
*
|
||||
* This overload is not available on Windows, where `std::filesystem::path`
|
||||
* converts to a `wchar_t` string rather than a `char` string.
|
||||
*/
|
||||
static oid
|
||||
from_file(dbtransaction &tx, std::filesystem::path const &path, oid id)
|
||||
{
|
||||
return from_file(tx, path.c_str(), id);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Convenience function: Read up to `max_size` bytes from blob with `id`.
|
||||
/** You could easily do this yourself using the @ref open_r and @ref read
|
||||
* functions, but it can save you a bit of code to do it this way.
|
||||
*/
|
||||
static void to_buf(
|
||||
dbtransaction &, oid, std::basic_string<std::byte> &,
|
||||
std::size_t max_size);
|
||||
|
||||
/// Read part of the binary large object with `id`, and append it to `buf`.
|
||||
/** Use this to break up a large read from one binary large object into one
|
||||
* massive buffer. Just keep calling this function until it returns zero.
|
||||
*
|
||||
* The `offset` is how far into the large object your desired chunk is, and
|
||||
* `append_max` says how much to try and read in one go.
|
||||
*/
|
||||
static std::size_t append_to_buf(
|
||||
dbtransaction &tx, oid id, std::int64_t offset,
|
||||
std::basic_string<std::byte> &buf, std::size_t append_max);
|
||||
|
||||
/// Write a binary large object's contents to a client-side file.
|
||||
static void to_file(dbtransaction &, oid, char const path[]);
|
||||
|
||||
#if defined(PQXX_HAVE_PATH) && !defined(_WIN32)
|
||||
/// Write a binary large object's contents to a client-side file.
|
||||
/** This overload is not available on Windows, where `std::filesystem::path`
|
||||
* converts to a `wchar_t` string rather than a `char` string.
|
||||
*/
|
||||
static void
|
||||
to_file(dbtransaction &tx, oid id, std::filesystem::path const &path)
|
||||
{
|
||||
to_file(tx, id, path.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Close this blob.
|
||||
/** This does not delete the blob from the database; it only terminates your
|
||||
* local object for accessing the blob.
|
||||
*
|
||||
* Resets the blob to a useless state similar to one that was
|
||||
* default-constructed.
|
||||
*
|
||||
* The destructor will do this for you automatically. Still, there is a
|
||||
* reason to `close()` objects explicitly where possible: if an error should
|
||||
* occur while closing, `close()` can throw an exception. A destructor
|
||||
* cannot.
|
||||
*/
|
||||
void close();
|
||||
|
||||
private:
|
||||
PQXX_PRIVATE blob(connection &conn, int fd) noexcept :
|
||||
m_conn{&conn}, m_fd{fd}
|
||||
{}
|
||||
static PQXX_PRIVATE blob open_internal(dbtransaction &, oid, int);
|
||||
static PQXX_PRIVATE pqxx::internal::pq::PGconn *
|
||||
raw_conn(pqxx::connection *) noexcept;
|
||||
static PQXX_PRIVATE pqxx::internal::pq::PGconn *
|
||||
raw_conn(pqxx::dbtransaction const &) noexcept;
|
||||
static PQXX_PRIVATE std::string errmsg(connection const *);
|
||||
static PQXX_PRIVATE std::string errmsg(dbtransaction const &tx)
|
||||
{
|
||||
return errmsg(&tx.conn());
|
||||
}
|
||||
PQXX_PRIVATE std::string errmsg() const { return errmsg(m_conn); }
|
||||
PQXX_PRIVATE std::int64_t seek(std::int64_t offset, int whence);
|
||||
std::size_t raw_read(std::byte buf[], std::size_t size);
|
||||
void raw_write(std::byte const buf[], std::size_t size);
|
||||
|
||||
connection *m_conn = nullptr;
|
||||
int m_fd = -1;
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
/** Handling of SQL "composite types."
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/composite.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,149 @@
|
||||
#ifndef PQXX_H_COMPOSITE
|
||||
#define PQXX_H_COMPOSITE
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/internal/array-composite.hxx"
|
||||
#include "pqxx/internal/concat.hxx"
|
||||
#include "pqxx/util.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Parse a string representation of a value of a composite type.
|
||||
/** @warning This code is still experimental. Use with care.
|
||||
*
|
||||
* You may use this as a helper while implementing your own @ref string_traits
|
||||
* for a composite type.
|
||||
*
|
||||
* This function interprets `text` as the string representation of a value of
|
||||
* some composite type, and sets each of `fields` to the respective values of
|
||||
* its fields. The field types must be copy-assignable.
|
||||
*
|
||||
* The number of fields must match the number of fields in the composite type,
|
||||
* and there must not be any other text in the input. The function is meant to
|
||||
* handle any value string that the backend can produce, but not necessarily
|
||||
* every valid alternative spelling.
|
||||
*
|
||||
* Fields in composite types can be null. When this happens, the C++ type of
|
||||
* the corresponding field reference must be of a type that can handle nulls.
|
||||
* If you are working with a type that does not have an inherent null value,
|
||||
* such as e.g. `int`, consider using `std::optional`.
|
||||
*/
|
||||
template<typename... T>
|
||||
inline void parse_composite(
|
||||
pqxx::internal::encoding_group enc, std::string_view text, T &...fields)
|
||||
{
|
||||
static_assert(sizeof...(fields) > 0);
|
||||
|
||||
auto const scan{pqxx::internal::get_glyph_scanner(enc)};
|
||||
auto const data{std::data(text)};
|
||||
auto const size{std::size(text)};
|
||||
if (size == 0)
|
||||
throw conversion_error{"Cannot parse composite value from empty string."};
|
||||
|
||||
std::size_t here{0}, next{scan(data, size, here)};
|
||||
if (next != 1 or data[here] != '(')
|
||||
throw conversion_error{
|
||||
internal::concat("Invalid composite value string: ", text)};
|
||||
|
||||
here = next;
|
||||
|
||||
constexpr auto num_fields{sizeof...(fields)};
|
||||
std::size_t index{0};
|
||||
(pqxx::internal::parse_composite_field(
|
||||
index, text, here, fields, scan, num_fields - 1),
|
||||
...);
|
||||
if (here != std::size(text))
|
||||
throw conversion_error{internal::concat(
|
||||
"Composite value did not end at the closing parenthesis: '", text,
|
||||
"'.")};
|
||||
if (text[here - 1] != ')')
|
||||
throw conversion_error{internal::concat(
|
||||
"Composive value did not end in parenthesis: '", text, "'")};
|
||||
}
|
||||
|
||||
|
||||
/// Parse a string representation of a value of a composite type.
|
||||
/** @warning This version only works for UTF-8 and single-byte encodings.
|
||||
*
|
||||
* For proper encoding support, use the composite-type support in the
|
||||
* `field` class.
|
||||
*/
|
||||
template<typename... T>
|
||||
inline void parse_composite(std::string_view text, T &...fields)
|
||||
{
|
||||
parse_composite(pqxx::internal::encoding_group::MONOBYTE, text, fields...);
|
||||
}
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
constexpr char empty_composite_str[]{"()"};
|
||||
} // namespace pqxx::internal
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Estimate the buffer size needed to represent a value of a composite type.
|
||||
/** Returns a conservative estimate.
|
||||
*/
|
||||
template<typename... T>
|
||||
[[nodiscard]] inline std::size_t
|
||||
composite_size_buffer(T const &...fields) noexcept
|
||||
{
|
||||
constexpr auto num{sizeof...(fields)};
|
||||
|
||||
// Size for a multi-field composite includes room for...
|
||||
// + opening parenthesis
|
||||
// + field budgets
|
||||
// + separating comma per field
|
||||
// - comma after final field
|
||||
// + closing parenthesis
|
||||
// + terminating zero
|
||||
|
||||
if constexpr (num == 0)
|
||||
return std::size(pqxx::internal::empty_composite_str);
|
||||
else
|
||||
return 1 + (pqxx::internal::size_composite_field_buffer(fields) + ...) +
|
||||
num + 1;
|
||||
}
|
||||
|
||||
|
||||
/// Render a series of values as a single composite SQL value.
|
||||
/** @warning This code is still experimental. Use with care.
|
||||
*
|
||||
* You may use this as a helper while implementing your own `string_traits`
|
||||
* for a composite type.
|
||||
*/
|
||||
template<typename... T>
|
||||
inline char *composite_into_buf(char *begin, char *end, T const &...fields)
|
||||
{
|
||||
if (std::size_t(end - begin) < composite_size_buffer(fields...))
|
||||
throw conversion_error{
|
||||
"Buffer space may not be enough to represent composite value."};
|
||||
|
||||
constexpr auto num_fields{sizeof...(fields)};
|
||||
if constexpr (num_fields == 0)
|
||||
{
|
||||
constexpr char empty[]{"()"};
|
||||
std::memcpy(begin, empty, std::size(empty));
|
||||
return begin + std::size(empty);
|
||||
}
|
||||
|
||||
char *pos{begin};
|
||||
*pos++ = '(';
|
||||
|
||||
(pqxx::internal::write_composite_field<T>(pos, end, fields), ...);
|
||||
|
||||
// If we've got multiple fields, "backspace" that last comma.
|
||||
if constexpr (num_fields > 1)
|
||||
--pos;
|
||||
*pos++ = ')';
|
||||
*pos++ = '\0';
|
||||
return pos;
|
||||
}
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,81 @@
|
||||
/* include/pqxx/config.h.in. Generated from configure.ac by autoheader. */
|
||||
/* Define to 1 if you have the <dlfcn.h> header file. */
|
||||
/* #undef HAVE_DLFCN_H */
|
||||
/* Define to 1 if you have the <inttypes.h> header file. */
|
||||
/* #undef HAVE_INTTYPES_H */
|
||||
/* Define to 1 if you have the `pq' library (-lpq). */
|
||||
/* #undef HAVE_LIBPQ */
|
||||
/* Define to 1 if you have the <memory.h> header file. */
|
||||
/* #undef HAVE_MEMORY_H */
|
||||
/* Define to 1 if you have the <stdint.h> header file. */
|
||||
/* #undef HAVE_STDINT_H */
|
||||
/* Define to 1 if you have the <stdlib.h> header file. */
|
||||
/* #undef HAVE_STDLIB_H */
|
||||
/* Define to 1 if you have the <strings.h> header file. */
|
||||
/* #undef HAVE_STRINGS_H */
|
||||
/* Define to 1 if you have the <string.h> header file. */
|
||||
/* #undef HAVE_STRING_H */
|
||||
/* Define to 1 if you have the <sys/stat.h> header file. */
|
||||
/* #undef HAVE_SYS_STAT_H */
|
||||
/* Define to 1 if you have the <sys/types.h> header file. */
|
||||
/* #undef HAVE_SYS_TYPES_H */
|
||||
/* Define to 1 if you have the <unistd.h> header file. */
|
||||
/* #undef HAVE_UNISTD_H */
|
||||
/* Define to the sub-directory where libtool stores uninstalled libraries. */
|
||||
/* #undef LT_OBJDIR */
|
||||
/* Name of package */
|
||||
/* #undef PACKAGE */
|
||||
/* Define to the address where bug reports for this package should be sent. */
|
||||
/* #undef PACKAGE_BUGREPORT */
|
||||
/* Define to the full name of this package. */
|
||||
/* #undef PACKAGE_NAME */
|
||||
/* Define to the full name and version of this package. */
|
||||
/* #undef PACKAGE_STRING */
|
||||
/* Define to the one symbol short name of this package. */
|
||||
/* #undef PACKAGE_TARNAME */
|
||||
/* Define to the home page for this package. */
|
||||
/* #undef PACKAGE_URL */
|
||||
/* Define to the version of this package. */
|
||||
/* #undef PACKAGE_VERSION */
|
||||
/* Define if <charconv> supports floating-point conversion. */
|
||||
#define PQXX_HAVE_CHARCONV_FLOAT
|
||||
/* Define if <charconv> supports integer conversion. */
|
||||
#define PQXX_HAVE_CHARCONV_INT
|
||||
/* Define if compiler has C++20 std::cmp_greater etc. */
|
||||
/* #undef PQXX_HAVE_CMP */
|
||||
/* Define if compiler supports Concepts and <ranges> header. */
|
||||
/* #undef PQXX_HAVE_CONCEPTS */
|
||||
/* Define if compiler supports __cxa_demangle */
|
||||
#define PQXX_HAVE_CXA_DEMANGLE
|
||||
/* Define if g++ supports pure attribute */
|
||||
#define PQXX_HAVE_GCC_PURE
|
||||
/* Define if g++ supports visibility attribute. */
|
||||
#define PQXX_HAVE_GCC_VISIBILITY
|
||||
/* Define if likely & unlikely work. */
|
||||
/* #undef PQXX_HAVE_LIKELY */
|
||||
/* Define if operator[] can take multiple arguments. */
|
||||
/* #undef PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT */
|
||||
/* Define if compiler has usable std::filesystem::path. */
|
||||
#define PQXX_HAVE_PATH
|
||||
/* Define if poll() is available. */
|
||||
#define PQXX_HAVE_POLL
|
||||
/* Define if libpq has PQencryptPasswordConn (since pg 10). */
|
||||
#define PQXX_HAVE_PQENCRYPTPASSWORDCONN
|
||||
/* Define if libpq has pipeline mode (since pg 14). */
|
||||
#define PQXX_HAVE_PQ_PIPELINE
|
||||
/* Define if std::this_thread::sleep_for works. */
|
||||
#define PQXX_HAVE_SLEEP_FOR
|
||||
/* Define if compiler has std::span. */
|
||||
/* #undef PQXX_HAVE_SPAN */
|
||||
/* Define if strerror_r() is available. */
|
||||
#define PQXX_HAVE_STRERROR_R
|
||||
/* Define if strerror_s() is available. */
|
||||
/* #undef PQXX_HAVE_STRERROR_S */
|
||||
/* Define if thread_local is fully supported. */
|
||||
#define PQXX_HAVE_THREAD_LOCAL
|
||||
/* Define if std::chrono has year_month_day etc. */
|
||||
/* #undef PQXX_HAVE_YEAR_MONTH_DAY */
|
||||
/* Define to 1 if you have the ANSI C header files. */
|
||||
/* #undef STDC_HEADERS */
|
||||
/* Version number of package */
|
||||
/* #undef VERSION */
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::connection class.
|
||||
*
|
||||
* pqxx::connection encapsulates a connection to a database.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/connection.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
/** Definition of the iterator/container-style cursor classes.
|
||||
*
|
||||
* C++-style wrappers for SQL cursors
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/cursor.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,483 @@
|
||||
/* Definition of the iterator/container-style cursor classes.
|
||||
*
|
||||
* C++-style wrappers for SQL cursors.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/cursor 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_CURSOR
|
||||
#define PQXX_H_CURSOR
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "pqxx/result.hxx"
|
||||
#include "pqxx/transaction_base.hxx"
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Common definitions for cursor types
|
||||
/** In C++ terms, fetches are always done in pre-increment or pre-decrement
|
||||
* fashion--i.e. the result does not include the row the cursor is on at the
|
||||
* beginning of the fetch, and the cursor ends up being positioned on the last
|
||||
* row in the result.
|
||||
*
|
||||
* There are singular positions akin to `end()` at both the beginning and the
|
||||
* end of the cursor's range of movement, although these fit in so naturally
|
||||
* with the semantics that one rarely notices them. The cursor begins at the
|
||||
* first of these, but any fetch in the forward direction will move the cursor
|
||||
* off this position and onto the first row before returning anything.
|
||||
*/
|
||||
class PQXX_LIBEXPORT cursor_base
|
||||
{
|
||||
public:
|
||||
using size_type = result_size_type;
|
||||
using difference_type = result_difference_type;
|
||||
|
||||
/// Cursor access-pattern policy
|
||||
/** Allowing a cursor to move forward only can result in better performance,
|
||||
* so use this access policy whenever possible.
|
||||
*/
|
||||
enum access_policy
|
||||
{
|
||||
/// Cursor can move forward only
|
||||
forward_only,
|
||||
/// Cursor can move back and forth
|
||||
random_access
|
||||
};
|
||||
|
||||
/// Cursor update policy
|
||||
/**
|
||||
* @warning Not all PostgreSQL versions support updatable cursors.
|
||||
*/
|
||||
enum update_policy
|
||||
{
|
||||
/// Cursor can be used to read data but not to write
|
||||
read_only,
|
||||
/// Cursor can be used to update data as well as read it
|
||||
update
|
||||
};
|
||||
|
||||
/// Cursor destruction policy
|
||||
/** The normal thing to do is to make a cursor object the owner of the SQL
|
||||
* cursor it represents. There may be cases, however, where a cursor needs
|
||||
* to persist beyond the end of the current transaction (and thus also beyond
|
||||
* the lifetime of the cursor object that created it!), where it can be
|
||||
* "adopted" into a new cursor object. See the basic_cursor documentation
|
||||
* for an explanation of cursor adoption.
|
||||
*
|
||||
* If a cursor is created with "loose" ownership policy, the object
|
||||
* representing the underlying SQL cursor will not take the latter with it
|
||||
* when its own lifetime ends, nor will its originating transaction.
|
||||
*
|
||||
* @warning Use this feature with care and moderation. Only one cursor
|
||||
* object should be responsible for any one underlying SQL cursor at any
|
||||
* given time.
|
||||
*/
|
||||
enum ownership_policy
|
||||
{
|
||||
/// Destroy SQL cursor when cursor object is closed at end of transaction
|
||||
owned,
|
||||
/// Leave SQL cursor in existence after close of object and transaction
|
||||
loose
|
||||
};
|
||||
|
||||
cursor_base() = delete;
|
||||
cursor_base(cursor_base const &) = delete;
|
||||
cursor_base &operator=(cursor_base const &) = delete;
|
||||
|
||||
/**
|
||||
* @name Special movement distances.
|
||||
*/
|
||||
//@{
|
||||
|
||||
// TODO: Make constexpr inline (but breaks ABI).
|
||||
/// Special value: read until end.
|
||||
/** @return Maximum value for result::difference_type, so the cursor will
|
||||
* attempt to read the largest possible result set.
|
||||
*/
|
||||
[[nodiscard]] static difference_type all() noexcept;
|
||||
|
||||
/// Special value: read one row only.
|
||||
/** @return Unsurprisingly, 1.
|
||||
*/
|
||||
[[nodiscard]] static constexpr difference_type next() noexcept { return 1; }
|
||||
|
||||
/// Special value: read backwards, one row only.
|
||||
/** @return Unsurprisingly, -1.
|
||||
*/
|
||||
[[nodiscard]] static constexpr difference_type prior() noexcept
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO: Make constexpr inline (but breaks ABI).
|
||||
/// Special value: read backwards from current position back to origin.
|
||||
/** @return Minimum value for result::difference_type.
|
||||
*/
|
||||
[[nodiscard]] static difference_type backward_all() noexcept;
|
||||
|
||||
//@}
|
||||
|
||||
/// Name of underlying SQL cursor
|
||||
/**
|
||||
* @returns Name of SQL cursor, which may differ from original given name.
|
||||
* @warning Don't use this to access the SQL cursor directly without going
|
||||
* through the provided wrapper classes!
|
||||
*/
|
||||
[[nodiscard]] constexpr std::string const &name() const noexcept
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
protected:
|
||||
cursor_base(connection &, std::string_view Name, bool embellish_name = true);
|
||||
|
||||
std::string const m_name;
|
||||
};
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
#include <pqxx/internal/sql_cursor.hxx>
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// "Stateless cursor" class: easy API for retrieving parts of result sets
|
||||
/** This is a front-end for SQL cursors, but with a more C++-like API.
|
||||
*
|
||||
* Actually, stateless_cursor feels entirely different from SQL cursors. You
|
||||
* don't keep track of positions, fetches, and moves; you just say which rows
|
||||
* you want. See the retrieve() member function.
|
||||
*/
|
||||
template<cursor_base::update_policy up, cursor_base::ownership_policy op>
|
||||
class stateless_cursor
|
||||
{
|
||||
public:
|
||||
using size_type = result_size_type;
|
||||
using difference_type = result_difference_type;
|
||||
|
||||
/// Create cursor.
|
||||
/**
|
||||
* @param tx The transaction within which you want to create the cursor.
|
||||
* @param query The SQL query whose results the cursor should traverse.
|
||||
* @param cname A hint for the cursor's name. The actual SQL cursor's name
|
||||
* will be based on this (though not necessarily identical).
|
||||
* @param hold Create a `WITH HOLD` cursor? Such cursors stay alive after
|
||||
* the transaction has ended, so you can continue to use it.
|
||||
*/
|
||||
stateless_cursor(
|
||||
transaction_base &tx, std::string_view query, std::string_view cname,
|
||||
bool hold) :
|
||||
m_cur{tx, query, cname, cursor_base::random_access, up, op, hold}
|
||||
{}
|
||||
|
||||
/// Adopt an existing scrolling SQL cursor.
|
||||
/** This lets you define a cursor yourself, and then wrap it in a
|
||||
* libpqxx-managed `stateless_cursor` object.
|
||||
*
|
||||
* @param tx The transaction within which you want to manage the cursor.
|
||||
* @param adopted_cursor Your cursor's SQL name.
|
||||
*/
|
||||
stateless_cursor(transaction_base &tx, std::string_view adopted_cursor) :
|
||||
m_cur{tx, adopted_cursor, op}
|
||||
{
|
||||
// Put cursor in known position
|
||||
m_cur.move(cursor_base::backward_all());
|
||||
}
|
||||
|
||||
/// Close this cursor.
|
||||
/** The destructor will do this for you automatically.
|
||||
*
|
||||
* Closing a cursor is idempotent. Closing a cursor that's already closed
|
||||
* does nothing.
|
||||
*/
|
||||
void close() noexcept { m_cur.close(); }
|
||||
|
||||
/// Number of rows in cursor's result set
|
||||
/** @note This function is not const; it may need to scroll to find the size
|
||||
* of the result set.
|
||||
*/
|
||||
[[nodiscard]] size_type size()
|
||||
{
|
||||
return internal::obtain_stateless_cursor_size(m_cur);
|
||||
}
|
||||
|
||||
/// Retrieve rows from begin_pos (inclusive) to end_pos (exclusive)
|
||||
/** Rows are numbered starting from 0 to size()-1.
|
||||
*
|
||||
* @param begin_pos First row to retrieve. May be one row beyond the end of
|
||||
* the result set, to avoid errors for empty result sets. Otherwise, must be
|
||||
* a valid row number in the result set.
|
||||
* @param end_pos Row up to which to fetch. Rows are returned ordered from
|
||||
* begin_pos to end_pos, i.e. in ascending order if begin_pos < end_pos but
|
||||
* in descending order if begin_pos > end_pos. The end_pos may be
|
||||
* arbitrarily inside or outside the result set; only existing rows are
|
||||
* included in the result.
|
||||
*/
|
||||
result retrieve(difference_type begin_pos, difference_type end_pos)
|
||||
{
|
||||
return internal::stateless_cursor_retrieve(
|
||||
m_cur, result::difference_type(size()), begin_pos, end_pos);
|
||||
}
|
||||
|
||||
/// Return this cursor's name.
|
||||
[[nodiscard]] constexpr std::string const &name() const noexcept
|
||||
{
|
||||
return m_cur.name();
|
||||
}
|
||||
|
||||
private:
|
||||
internal::sql_cursor m_cur;
|
||||
};
|
||||
|
||||
|
||||
class icursor_iterator;
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class icursor_iterator_icursorstream;
|
||||
class icursorstream_icursor_iterator;
|
||||
} // namespace pqxx::internal::gate
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Simple read-only cursor represented as a stream of results
|
||||
/** SQL cursors can be tricky, especially in C++ since the two languages seem
|
||||
* to have been designed on different planets. An SQL cursor has two singular
|
||||
* positions akin to `end()` on either side of the underlying result set.
|
||||
*
|
||||
* These cultural differences are hidden from view somewhat by libpqxx, which
|
||||
* tries to make SQL cursors behave more like familiar C++ entities such as
|
||||
* iterators, sequences, streams, and containers.
|
||||
*
|
||||
* Data is fetched from the cursor as a sequence of result objects. Each of
|
||||
* these will contain the number of rows defined as the stream's stride, except
|
||||
* of course the last block of data which may contain fewer rows.
|
||||
*
|
||||
* This class can create or adopt cursors that live outside any backend
|
||||
* transaction, which your backend version may not support.
|
||||
*/
|
||||
class PQXX_LIBEXPORT icursorstream
|
||||
{
|
||||
public:
|
||||
using size_type = cursor_base::size_type;
|
||||
using difference_type = cursor_base::difference_type;
|
||||
|
||||
/// Set up a read-only, forward-only cursor.
|
||||
/** Roughly equivalent to a C++ Standard Library istream, this cursor type
|
||||
* supports only two operations: reading a block of rows while moving
|
||||
* forward, and moving forward without reading any data.
|
||||
*
|
||||
* @param context Transaction context in which this cursor will be active.
|
||||
* @param query SQL query whose results this cursor shall iterate.
|
||||
* @param basename Suggested name for the SQL cursor; the library will append
|
||||
* a unique code to ensure its uniqueness.
|
||||
* @param sstride Number of rows to fetch per read operation; must be a
|
||||
* positive number.
|
||||
*/
|
||||
icursorstream(
|
||||
transaction_base &context, std::string_view query,
|
||||
std::string_view basename, difference_type sstride = 1);
|
||||
|
||||
/// Adopt existing SQL cursor. Use with care.
|
||||
/** Forms a cursor stream around an existing SQL cursor, as returned by e.g.
|
||||
* a server-side function. The SQL cursor will be cleaned up by the stream's
|
||||
* destructor as if it had been created by the stream; cleaning it up by hand
|
||||
* or adopting the same cursor twice is an error.
|
||||
*
|
||||
* Passing the name of the cursor as a string is not allowed, both to avoid
|
||||
* confusion with the other constructor and to discourage unnecessary use of
|
||||
* adopted cursors.
|
||||
*
|
||||
* @warning It is technically possible to adopt a "WITH HOLD" cursor, i.e. a
|
||||
* cursor that stays alive outside its creating transaction. However, any
|
||||
* cursor stream (including the underlying SQL cursor, naturally) must be
|
||||
* destroyed before its transaction context object is destroyed. Therefore
|
||||
* the only way to use SQL's WITH HOLD feature is to adopt the cursor, but
|
||||
* defer doing so until after entering the transaction context that will
|
||||
* eventually destroy it.
|
||||
*
|
||||
* @param context Transaction context in which this cursor will be active.
|
||||
* @param cname Result field containing the name of the SQL cursor to adopt.
|
||||
* @param sstride Number of rows to fetch per read operation; must be a
|
||||
* positive number.
|
||||
* @param op Ownership policy. Determines whether the cursor underlying this
|
||||
* stream will be destroyed when the stream is closed.
|
||||
*/
|
||||
icursorstream(
|
||||
transaction_base &context, field const &cname, difference_type sstride = 1,
|
||||
cursor_base::ownership_policy op = cursor_base::owned);
|
||||
|
||||
/// Return `true` if this stream may still return more data.
|
||||
constexpr operator bool() const &noexcept { return not m_done; }
|
||||
|
||||
/// Read new value into given result object; same as operator `>>`.
|
||||
/** The result set may continue any number of rows from zero to the chosen
|
||||
* stride, inclusive. An empty result will only be returned if there are no
|
||||
* more rows to retrieve.
|
||||
*
|
||||
* @param res Write the retrieved data into this result object.
|
||||
* @return Reference to this very stream, to facilitate "chained" invocations
|
||||
* ("C.get(r1).get(r2);")
|
||||
*/
|
||||
icursorstream &get(result &res)
|
||||
{
|
||||
res = fetchblock();
|
||||
return *this;
|
||||
}
|
||||
/// Read new value into given result object; same as `get(result&)`.
|
||||
/** The result set may continue any number of rows from zero to the chosen
|
||||
* stride, inclusive. An empty result will only be returned if there are no
|
||||
* more rows to retrieve.
|
||||
*
|
||||
* @param res Write the retrieved data into this result object.
|
||||
* @return Reference to this very stream, to facilitate "chained" invocations
|
||||
* ("C >> r1 >> r2;")
|
||||
*/
|
||||
icursorstream &operator>>(result &res) { return get(res); }
|
||||
|
||||
/// Move given number of rows forward without reading data.
|
||||
/** Ignores any stride that you may have set. It moves by a given number of
|
||||
* rows, not a number of strides.
|
||||
*
|
||||
* @return Reference to this stream itself, to facilitate "chained"
|
||||
* invocations.
|
||||
*/
|
||||
icursorstream &ignore(std::streamsize n = 1) &;
|
||||
|
||||
/// Change stride, i.e. the number of rows to fetch per read operation.
|
||||
/**
|
||||
* @param stride Must be a positive number.
|
||||
*/
|
||||
void set_stride(difference_type stride) &;
|
||||
[[nodiscard]] constexpr difference_type stride() const noexcept
|
||||
{
|
||||
return m_stride;
|
||||
}
|
||||
|
||||
private:
|
||||
result fetchblock();
|
||||
|
||||
friend class internal::gate::icursorstream_icursor_iterator;
|
||||
size_type forward(size_type n = 1);
|
||||
void insert_iterator(icursor_iterator *) noexcept;
|
||||
void remove_iterator(icursor_iterator *) const noexcept;
|
||||
|
||||
void service_iterators(difference_type);
|
||||
|
||||
internal::sql_cursor m_cur;
|
||||
|
||||
difference_type m_stride;
|
||||
difference_type m_realpos, m_reqpos;
|
||||
|
||||
mutable icursor_iterator *m_iterators;
|
||||
|
||||
bool m_done;
|
||||
};
|
||||
|
||||
|
||||
/// Approximate istream_iterator for icursorstream.
|
||||
/** Intended as an implementation of an input_iterator (as defined by the C++
|
||||
* Standard Library), this class supports only two basic operations: reading
|
||||
* the current element, and moving forward. In addition to the minimal
|
||||
* guarantees for istream_iterators, this class supports multiple successive
|
||||
* reads of the same position (the current result set is cached in the
|
||||
* iterator) even after copying and even after new data have been read from the
|
||||
* stream. This appears to be a requirement for input_iterators. Comparisons
|
||||
* are also supported in the general case.
|
||||
*
|
||||
* The iterator does not care about its own position, however. Moving an
|
||||
* iterator forward moves the underlying stream forward and reads the data from
|
||||
* the new stream position, regardless of the iterator's old position in the
|
||||
* stream.
|
||||
*
|
||||
* The stream's stride defines the granularity for all iterator movement or
|
||||
* access operations, i.e. "ici += 1" advances the stream by one stride's worth
|
||||
* of rows, and "*ici++" reads one stride's worth of rows from the stream.
|
||||
*
|
||||
* @warning Do not read from the underlying stream or its cursor, move its read
|
||||
* position, or change its stride, between the time the first icursor_iterator
|
||||
* on it is created and the time its last icursor_iterator is destroyed.
|
||||
*
|
||||
* @warning Manipulating these iterators within the context of a single cursor
|
||||
* stream is <em>not thread-safe</em>. Creating a new iterator, copying one,
|
||||
* or destroying one affects the stream as a whole.
|
||||
*/
|
||||
class PQXX_LIBEXPORT icursor_iterator
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = result;
|
||||
using pointer = result const *;
|
||||
using reference = result const &;
|
||||
using istream_type = icursorstream;
|
||||
using size_type = istream_type::size_type;
|
||||
using difference_type = istream_type::difference_type;
|
||||
|
||||
icursor_iterator() noexcept;
|
||||
explicit icursor_iterator(istream_type &) noexcept;
|
||||
icursor_iterator(icursor_iterator const &) noexcept;
|
||||
~icursor_iterator() noexcept;
|
||||
|
||||
result const &operator*() const
|
||||
{
|
||||
refresh();
|
||||
return m_here;
|
||||
}
|
||||
result const *operator->() const
|
||||
{
|
||||
refresh();
|
||||
return &m_here;
|
||||
}
|
||||
icursor_iterator &operator++();
|
||||
icursor_iterator operator++(int);
|
||||
icursor_iterator &operator+=(difference_type);
|
||||
icursor_iterator &operator=(icursor_iterator const &) noexcept;
|
||||
|
||||
[[nodiscard]] bool operator==(icursor_iterator const &rhs) const;
|
||||
[[nodiscard]] bool operator!=(icursor_iterator const &rhs) const noexcept
|
||||
{
|
||||
return not operator==(rhs);
|
||||
}
|
||||
[[nodiscard]] bool operator<(icursor_iterator const &rhs) const;
|
||||
[[nodiscard]] bool operator>(icursor_iterator const &rhs) const
|
||||
{
|
||||
return rhs < *this;
|
||||
}
|
||||
[[nodiscard]] bool operator<=(icursor_iterator const &rhs) const
|
||||
{
|
||||
return not(*this > rhs);
|
||||
}
|
||||
[[nodiscard]] bool operator>=(icursor_iterator const &rhs) const
|
||||
{
|
||||
return not(*this < rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
void refresh() const;
|
||||
|
||||
friend class internal::gate::icursor_iterator_icursorstream;
|
||||
difference_type pos() const noexcept { return m_pos; }
|
||||
void fill(result const &);
|
||||
|
||||
icursorstream *m_stream{nullptr};
|
||||
result m_here;
|
||||
difference_type m_pos;
|
||||
icursor_iterator *m_prev{nullptr}, *m_next{nullptr};
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::dbtransaction abstract base class.
|
||||
*
|
||||
* pqxx::dbransaction defines a real transaction on the database.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/dbtransaction.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,70 @@
|
||||
/* Definition of the pqxx::dbtransaction abstract base class.
|
||||
*
|
||||
* pqxx::dbransaction defines a real transaction on the database.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/dbtransaction 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_DBTRANSACTION
|
||||
#define PQXX_H_DBTRANSACTION
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/transaction_base.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Abstract transaction base class: bracket transactions on the database.
|
||||
/**
|
||||
* @ingroup transactions
|
||||
*
|
||||
* Use a dbtransaction-derived object such as "work" (transaction<>) to enclose
|
||||
* operations on a database in a single "unit of work." This ensures that the
|
||||
* whole series of operations either succeeds as a whole or fails completely.
|
||||
* In no case will it leave half-finished work behind in the database.
|
||||
*
|
||||
* Once processing on a transaction has succeeded and any changes should be
|
||||
* allowed to become permanent in the database, call commit(). If something
|
||||
* has gone wrong and the changes should be forgotten, call abort() instead.
|
||||
* If you do neither, an implicit abort() is executed at destruction time.
|
||||
*
|
||||
* It is an error to abort a transaction that has already been committed, or to
|
||||
* commit a transaction that has already been aborted. Aborting an already
|
||||
* aborted transaction or committing an already committed one is allowed, to
|
||||
* make error handling easier. Repeated aborts or commits have no effect after
|
||||
* the first one.
|
||||
*
|
||||
* Database transactions are not suitable for guarding long-running processes.
|
||||
* If your transaction code becomes too long or too complex, consider ways to
|
||||
* break it up into smaller ones. Unfortunately there is no universal recipe
|
||||
* for this.
|
||||
*
|
||||
* The actual operations for committing/aborting the backend transaction are
|
||||
* implemented by a derived class. The implementing concrete class must also
|
||||
* call @ref close from its destructor.
|
||||
*/
|
||||
class PQXX_LIBEXPORT PQXX_NOVTABLE dbtransaction : public transaction_base
|
||||
{
|
||||
protected:
|
||||
/// Begin transaction.
|
||||
explicit dbtransaction(connection &c) : transaction_base{c} {}
|
||||
/// Begin transaction.
|
||||
dbtransaction(connection &c, std::string_view tname) :
|
||||
transaction_base{c, tname}
|
||||
{}
|
||||
/// Begin transaction.
|
||||
dbtransaction(
|
||||
connection &c, std::string_view tname,
|
||||
std::shared_ptr<std::string> rollback_cmd) :
|
||||
transaction_base{c, tname, rollback_cmd}
|
||||
{}
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::errorhandler class.
|
||||
*
|
||||
* Callbacks for handling errors and warnings.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/errorhandler.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,92 @@
|
||||
/* Definition of the pqxx::errorhandler class.
|
||||
*
|
||||
* pqxx::errorhandler handlers errors and warnings in a database session.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/errorhandler 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_ERRORHANDLER
|
||||
#define PQXX_H_ERRORHANDLER
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/types.hxx"
|
||||
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class errorhandler_connection;
|
||||
}
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/**
|
||||
* @addtogroup errorhandler
|
||||
*/
|
||||
//@{
|
||||
|
||||
/// Base class for error-handler callbacks.
|
||||
/** To receive errors and warnings from a connection, subclass this with your
|
||||
* own error-handler functor, and instantiate it for the connection. Destroying
|
||||
* the handler un-registers it.
|
||||
*
|
||||
* A connection can have multiple error handlers at the same time. When the
|
||||
* database connection emits an error or warning message, it passes the message
|
||||
* to each error handler, starting with the most recently registered one and
|
||||
* progressing towards the oldest one. However an error handler may also
|
||||
* instruct the connection not to pass the message to further handlers by
|
||||
* returning "false."
|
||||
*
|
||||
* @warning Strange things happen when a result object outlives its parent
|
||||
* connection. If you register an error handler on a connection, then you must
|
||||
* not access the result after destroying the connection. This applies even if
|
||||
* you destroy the error handler first!
|
||||
*/
|
||||
class PQXX_LIBEXPORT errorhandler
|
||||
{
|
||||
public:
|
||||
explicit errorhandler(connection &);
|
||||
virtual ~errorhandler();
|
||||
|
||||
/// Define in subclass: receive an error or warning message from the
|
||||
/// database.
|
||||
/**
|
||||
* @return Whether the same error message should also be passed to the
|
||||
* remaining, older errorhandlers.
|
||||
*/
|
||||
virtual bool operator()(char const msg[]) noexcept = 0;
|
||||
|
||||
errorhandler() = delete;
|
||||
errorhandler(errorhandler const &) = delete;
|
||||
errorhandler &operator=(errorhandler const &) = delete;
|
||||
|
||||
private:
|
||||
connection *m_home;
|
||||
|
||||
friend class internal::gate::errorhandler_connection;
|
||||
void unregister() noexcept;
|
||||
};
|
||||
|
||||
|
||||
/// An error handler that suppresses any previously registered error handlers.
|
||||
class quiet_errorhandler : public errorhandler
|
||||
{
|
||||
public:
|
||||
/// Suppress error notices.
|
||||
quiet_errorhandler(connection &conn) : errorhandler{conn} {}
|
||||
|
||||
/// Revert to previous handling of error notices.
|
||||
virtual bool operator()(char const[]) noexcept override { return false; }
|
||||
};
|
||||
|
||||
//@}
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** libpqxx exception classes.
|
||||
*
|
||||
* pqxx::sql_error, pqxx::broken_connection, pqxx::in_doubt_error, ...
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/except.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,447 @@
|
||||
/* Definition of libpqxx exception classes.
|
||||
*
|
||||
* pqxx::sql_error, pqxx::broken_connection, pqxx::in_doubt_error, ...
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/except 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_EXCEPT
|
||||
#define PQXX_H_EXCEPT
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/**
|
||||
* @addtogroup exception Exception classes
|
||||
*
|
||||
* These exception classes follow, roughly, the two-level hierarchy defined by
|
||||
* the PostgreSQL SQLSTATE error codes (see Appendix A of the PostgreSQL
|
||||
* documentation corresponding to your server version). This is not a complete
|
||||
* mapping though. There are other differences as well, e.g. the error code
|
||||
* for `statement_completion_unknown` has a separate status in libpqxx as
|
||||
* @ref in_doubt_error, and `too_many_connections` is classified as a
|
||||
* `broken_connection` rather than a subtype of `insufficient_resources`.
|
||||
*
|
||||
* @see http://www.postgresql.org/docs/9.4/interactive/errcodes-appendix.html
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/// Run-time failure encountered by libpqxx, similar to std::runtime_error.
|
||||
struct PQXX_LIBEXPORT failure : std::runtime_error
|
||||
{
|
||||
explicit failure(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// Exception class for lost or failed backend connection.
|
||||
/**
|
||||
* @warning When this happens on Unix-like systems, you may also get a SIGPIPE
|
||||
* signal. That signal aborts the program by default, so if you wish to be
|
||||
* able to continue after a connection breaks, be sure to disarm this signal.
|
||||
*
|
||||
* If you're working on a Unix-like system, see the manual page for
|
||||
* `signal` (2) on how to deal with SIGPIPE. The easiest way to make this
|
||||
* signal harmless is to make your program ignore it:
|
||||
*
|
||||
* ```cxx
|
||||
* #include <signal.h>
|
||||
*
|
||||
* int main()
|
||||
* {
|
||||
* signal(SIGPIPE, SIG_IGN);
|
||||
* // ...
|
||||
* ```
|
||||
*/
|
||||
struct PQXX_LIBEXPORT broken_connection : failure
|
||||
{
|
||||
broken_connection();
|
||||
explicit broken_connection(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// The caller attempted to set a variable to null, which is not allowed.
|
||||
struct PQXX_LIBEXPORT variable_set_to_null : failure
|
||||
{
|
||||
variable_set_to_null();
|
||||
explicit variable_set_to_null(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// Exception class for failed queries.
|
||||
/** Carries, in addition to a regular error message, a copy of the failed query
|
||||
* and (if available) the SQLSTATE value accompanying the error.
|
||||
*/
|
||||
class PQXX_LIBEXPORT sql_error : public failure
|
||||
{
|
||||
/// Query string. Empty if unknown.
|
||||
std::string const m_query;
|
||||
/// SQLSTATE string describing the error type, if known; or empty string.
|
||||
std::string const m_sqlstate;
|
||||
|
||||
public:
|
||||
explicit sql_error(
|
||||
std::string const &whatarg = "", std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr);
|
||||
virtual ~sql_error() noexcept override;
|
||||
|
||||
/// The query whose execution triggered the exception
|
||||
[[nodiscard]] PQXX_PURE std::string const &query() const noexcept;
|
||||
|
||||
/// SQLSTATE error code if known, or empty string otherwise.
|
||||
[[nodiscard]] PQXX_PURE std::string const &sqlstate() const noexcept;
|
||||
};
|
||||
|
||||
|
||||
/// "Help, I don't know whether transaction was committed successfully!"
|
||||
/** Exception that might be thrown in rare cases where the connection to the
|
||||
* database is lost while finishing a database transaction, and there's no way
|
||||
* of telling whether it was actually executed by the backend. In this case
|
||||
* the database is left in an indeterminate (but consistent) state, and only
|
||||
* manual inspection will tell which is the case.
|
||||
*/
|
||||
struct PQXX_LIBEXPORT in_doubt_error : failure
|
||||
{
|
||||
explicit in_doubt_error(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// The backend saw itself forced to roll back the ongoing transaction.
|
||||
struct PQXX_LIBEXPORT transaction_rollback : sql_error
|
||||
{
|
||||
explicit transaction_rollback(
|
||||
std::string const &whatarg, std::string const &q = "",
|
||||
char const sqlstate[] = nullptr);
|
||||
};
|
||||
|
||||
|
||||
/// Transaction failed to serialize. Please retry it.
|
||||
/** Can only happen at transaction isolation levels REPEATABLE READ and
|
||||
* SERIALIZABLE.
|
||||
*
|
||||
* The current transaction cannot be committed without violating the guarantees
|
||||
* made by its isolation level. This is the effect of a conflict with another
|
||||
* ongoing transaction. The transaction may still succeed if you try to
|
||||
* perform it again.
|
||||
*/
|
||||
struct PQXX_LIBEXPORT serialization_failure : transaction_rollback
|
||||
{
|
||||
explicit serialization_failure(
|
||||
std::string const &whatarg, std::string const &q,
|
||||
char const sqlstate[] = nullptr);
|
||||
};
|
||||
|
||||
|
||||
/// We can't tell whether our last statement succeeded.
|
||||
struct PQXX_LIBEXPORT statement_completion_unknown : transaction_rollback
|
||||
{
|
||||
explicit statement_completion_unknown(
|
||||
std::string const &whatarg, std::string const &q,
|
||||
char const sqlstate[] = nullptr);
|
||||
};
|
||||
|
||||
|
||||
/// The ongoing transaction has deadlocked. Retrying it may help.
|
||||
struct PQXX_LIBEXPORT deadlock_detected : transaction_rollback
|
||||
{
|
||||
explicit deadlock_detected(
|
||||
std::string const &whatarg, std::string const &q,
|
||||
char const sqlstate[] = nullptr);
|
||||
};
|
||||
|
||||
|
||||
/// Internal error in libpqxx library
|
||||
struct PQXX_LIBEXPORT internal_error : std::logic_error
|
||||
{
|
||||
explicit internal_error(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// Error in usage of libpqxx library, similar to std::logic_error
|
||||
struct PQXX_LIBEXPORT usage_error : std::logic_error
|
||||
{
|
||||
explicit usage_error(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// Invalid argument passed to libpqxx, similar to std::invalid_argument
|
||||
struct PQXX_LIBEXPORT argument_error : std::invalid_argument
|
||||
{
|
||||
explicit argument_error(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// Value conversion failed, e.g. when converting "Hello" to int.
|
||||
struct PQXX_LIBEXPORT conversion_error : std::domain_error
|
||||
{
|
||||
explicit conversion_error(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// Could not convert value to string: not enough buffer space.
|
||||
struct PQXX_LIBEXPORT conversion_overrun : conversion_error
|
||||
{
|
||||
explicit conversion_overrun(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// Something is out of range, similar to std::out_of_range
|
||||
struct PQXX_LIBEXPORT range_error : std::out_of_range
|
||||
{
|
||||
explicit range_error(std::string const &);
|
||||
};
|
||||
|
||||
|
||||
/// Query returned an unexpected number of rows.
|
||||
struct PQXX_LIBEXPORT unexpected_rows : public range_error
|
||||
{
|
||||
explicit unexpected_rows(std::string const &msg) : range_error{msg} {}
|
||||
};
|
||||
|
||||
|
||||
/// Database feature not supported in current setup.
|
||||
struct PQXX_LIBEXPORT feature_not_supported : sql_error
|
||||
{
|
||||
explicit feature_not_supported(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
sql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
/// Error in data provided to SQL statement.
|
||||
struct PQXX_LIBEXPORT data_exception : sql_error
|
||||
{
|
||||
explicit data_exception(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
sql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT integrity_constraint_violation : sql_error
|
||||
{
|
||||
explicit integrity_constraint_violation(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
sql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT restrict_violation : integrity_constraint_violation
|
||||
{
|
||||
explicit restrict_violation(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
integrity_constraint_violation{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT not_null_violation : integrity_constraint_violation
|
||||
{
|
||||
explicit not_null_violation(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
integrity_constraint_violation{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT foreign_key_violation : integrity_constraint_violation
|
||||
{
|
||||
explicit foreign_key_violation(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
integrity_constraint_violation{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT unique_violation : integrity_constraint_violation
|
||||
{
|
||||
explicit unique_violation(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
integrity_constraint_violation{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT check_violation : integrity_constraint_violation
|
||||
{
|
||||
explicit check_violation(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
integrity_constraint_violation{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT invalid_cursor_state : sql_error
|
||||
{
|
||||
explicit invalid_cursor_state(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
sql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT invalid_sql_statement_name : sql_error
|
||||
{
|
||||
explicit invalid_sql_statement_name(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
sql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT invalid_cursor_name : sql_error
|
||||
{
|
||||
explicit invalid_cursor_name(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
sql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT syntax_error : sql_error
|
||||
{
|
||||
/// Approximate position in string where error occurred, or -1 if unknown.
|
||||
int const error_position;
|
||||
|
||||
explicit syntax_error(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr, int pos = -1) :
|
||||
sql_error{err, Q, sqlstate}, error_position{pos}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT undefined_column : syntax_error
|
||||
{
|
||||
explicit undefined_column(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
syntax_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT undefined_function : syntax_error
|
||||
{
|
||||
explicit undefined_function(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
syntax_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT undefined_table : syntax_error
|
||||
{
|
||||
explicit undefined_table(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
syntax_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT insufficient_privilege : sql_error
|
||||
{
|
||||
explicit insufficient_privilege(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
sql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
/// Resource shortage on the server
|
||||
struct PQXX_LIBEXPORT insufficient_resources : sql_error
|
||||
{
|
||||
explicit insufficient_resources(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
sql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT disk_full : insufficient_resources
|
||||
{
|
||||
explicit disk_full(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
insufficient_resources{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT out_of_memory : insufficient_resources
|
||||
{
|
||||
explicit out_of_memory(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
insufficient_resources{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT too_many_connections : broken_connection
|
||||
{
|
||||
explicit too_many_connections(std::string const &err) :
|
||||
broken_connection{err}
|
||||
{}
|
||||
};
|
||||
|
||||
/// PL/pgSQL error
|
||||
/** Exceptions derived from this class are errors from PL/pgSQL procedures.
|
||||
*/
|
||||
struct PQXX_LIBEXPORT plpgsql_error : sql_error
|
||||
{
|
||||
explicit plpgsql_error(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
sql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
/// Exception raised in PL/pgSQL procedure
|
||||
struct PQXX_LIBEXPORT plpgsql_raise : plpgsql_error
|
||||
{
|
||||
explicit plpgsql_raise(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
plpgsql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT plpgsql_no_data_found : plpgsql_error
|
||||
{
|
||||
explicit plpgsql_no_data_found(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
plpgsql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT plpgsql_too_many_rows : plpgsql_error
|
||||
{
|
||||
explicit plpgsql_too_many_rows(
|
||||
std::string const &err, std::string const &Q = "",
|
||||
char const sqlstate[] = nullptr) :
|
||||
plpgsql_error{err, Q, sqlstate}
|
||||
{}
|
||||
};
|
||||
|
||||
struct PQXX_LIBEXPORT blob_already_exists : failure
|
||||
{
|
||||
explicit blob_already_exists(std::string const &);
|
||||
};
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::field class.
|
||||
*
|
||||
* pqxx::field refers to a field in a query result.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/field.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,542 @@
|
||||
/* Definitions for the pqxx::field class.
|
||||
*
|
||||
* pqxx::field refers to a field in a query result.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/field 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_FIELD
|
||||
#define PQXX_H_FIELD
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "pqxx/array.hxx"
|
||||
#include "pqxx/composite.hxx"
|
||||
#include "pqxx/result.hxx"
|
||||
#include "pqxx/strconv.hxx"
|
||||
#include "pqxx/types.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Reference to a field in a result set.
|
||||
/** A field represents one entry in a row. It represents an actual value
|
||||
* in the result set, and can be converted to various types.
|
||||
*/
|
||||
class PQXX_LIBEXPORT field
|
||||
{
|
||||
public:
|
||||
using size_type = field_size_type;
|
||||
|
||||
/// Constructor. Do not call this yourself; libpqxx will do it for you.
|
||||
/** Create field as reference to a field in a result set.
|
||||
* @param r Row that this field is part of.
|
||||
* @param c Column number of this field.
|
||||
*/
|
||||
[[deprecated(
|
||||
"Do not construct fields yourself. Get them from the row.")]] field(row const &r, row_size_type c) noexcept;
|
||||
|
||||
/// Constructor. Do not call this yourself; libpqxx will do it for you.
|
||||
[[deprecated(
|
||||
"Do not construct fields yourself. Get them from the "
|
||||
"row.")]] field() noexcept = default;
|
||||
|
||||
/**
|
||||
* @name Comparison
|
||||
*/
|
||||
//@{
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
/// Byte-by-byte comparison of two fields (all nulls are considered equal)
|
||||
/** @warning null handling is still open to discussion and change!
|
||||
*
|
||||
* Handling of null values differs from that in SQL where a comparison
|
||||
* involving a null value yields null, so nulls are never considered equal
|
||||
* to one another or even to themselves.
|
||||
*
|
||||
* Null handling also probably differs from the closest equivalent in C++,
|
||||
* which is the NaN (Not-a-Number) value, a singularity comparable to
|
||||
* SQL's null. This is because the builtin == operator demands that a == a.
|
||||
*
|
||||
* The usefulness of this operator is questionable. No interpretation
|
||||
* whatsoever is imposed on the data; 0 and 0.0 are considered different,
|
||||
* as are null vs. the empty string, or even different (but possibly
|
||||
* equivalent and equally valid) encodings of the same Unicode character
|
||||
* etc.
|
||||
*/
|
||||
[[nodiscard]] PQXX_PURE bool operator==(field const &) const;
|
||||
|
||||
/// Byte-by-byte comparison (all nulls are considered equal)
|
||||
/** @warning See operator==() for important information about this operator
|
||||
*/
|
||||
[[nodiscard]] PQXX_PURE bool operator!=(field const &rhs) const noexcept
|
||||
{
|
||||
return not operator==(rhs);
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Column information
|
||||
*/
|
||||
//@{
|
||||
/// Column name.
|
||||
[[nodiscard]] PQXX_PURE char const *name() const &;
|
||||
|
||||
/// Column type.
|
||||
[[nodiscard]] oid PQXX_PURE type() const;
|
||||
|
||||
/// What table did this column come from?
|
||||
[[nodiscard]] PQXX_PURE oid table() const;
|
||||
|
||||
/// Return row number. The first row is row 0, the second is row 1, etc.
|
||||
PQXX_PURE constexpr row_size_type num() const noexcept { return col(); }
|
||||
|
||||
/// What column number in its originating table did this column come from?
|
||||
[[nodiscard]] PQXX_PURE row_size_type table_column() const;
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Content access
|
||||
*/
|
||||
//@{
|
||||
/// Read as `string_view`, or an empty one if null.
|
||||
/** The result only remains usable while the data for the underlying
|
||||
* @ref result exists. Once all `result` objects referring to that data have
|
||||
* been destroyed, the `string_view` will no longer point to valid memory.
|
||||
*/
|
||||
[[nodiscard]] PQXX_PURE std::string_view view() const &
|
||||
{
|
||||
return std::string_view(c_str(), size());
|
||||
}
|
||||
|
||||
/// Read as plain C string.
|
||||
/** Since the field's data is stored internally in the form of a
|
||||
* zero-terminated C string, this is the fastest way to read it. Use the
|
||||
* to() or as() functions to convert the string to other types such as
|
||||
* `int`, or to C++ strings.
|
||||
*
|
||||
* Do not use this for BYTEA values, or other binary values. To read those,
|
||||
* convert the value to your desired type using `to()` or `as()`. For
|
||||
* example: `f.as<std::basic_string<std::byte>>()`.
|
||||
*/
|
||||
[[nodiscard]] PQXX_PURE char const *c_str() const &;
|
||||
|
||||
/// Is this field's value null?
|
||||
[[nodiscard]] PQXX_PURE bool is_null() const noexcept;
|
||||
|
||||
/// Return number of bytes taken up by the field's value.
|
||||
[[nodiscard]] PQXX_PURE size_type size() const noexcept;
|
||||
|
||||
/// Read value into obj; or if null, leave obj untouched and return `false`.
|
||||
/** This can be used with optional types (except pointers other than C-style
|
||||
* strings).
|
||||
*/
|
||||
template<typename T>
|
||||
auto to(T &obj) const -> typename std::enable_if_t<
|
||||
(not std::is_pointer<T>::value or std::is_same<T, char const *>::value),
|
||||
bool>
|
||||
{
|
||||
if (is_null())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const bytes{c_str()};
|
||||
from_string(bytes, obj);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Read field as a composite value, write its components into `fields`.
|
||||
/** @warning This is still experimental. It may change or be replaced.
|
||||
*
|
||||
* Returns whether the field was null. If it was, it will not touch the
|
||||
* values in `fields`.
|
||||
*/
|
||||
template<typename... T> bool composite_to(T &...fields) const
|
||||
{
|
||||
if (is_null())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
parse_composite(m_home.m_encoding, view(), fields...);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Read value into obj; or leave obj untouched and return `false` if null.
|
||||
template<typename T> bool operator>>(T &obj) const { return to(obj); }
|
||||
|
||||
/// Read value into obj; or if null, use default value and return `false`.
|
||||
/** This can be used with `std::optional`, as well as with standard smart
|
||||
* pointer types, but not with raw pointers. If the conversion from a
|
||||
* PostgreSQL string representation allocates a pointer (e.g. using `new`),
|
||||
* then the object's later deallocation should be baked in as well, right
|
||||
* from the point where the object is created. So if you want a pointer, use
|
||||
* a smart pointer, not a raw pointer.
|
||||
*
|
||||
* There is one exception, of course: C-style strings. Those are just
|
||||
* pointers to the field's internal text data.
|
||||
*/
|
||||
template<typename T>
|
||||
auto to(T &obj, T const &default_value) const -> typename std::enable_if_t<
|
||||
(not std::is_pointer<T>::value or std::is_same<T, char const *>::value),
|
||||
bool>
|
||||
{
|
||||
bool const null{is_null()};
|
||||
if (null)
|
||||
obj = default_value;
|
||||
else
|
||||
obj = from_string<T>(this->view());
|
||||
return not null;
|
||||
}
|
||||
|
||||
/// Return value as object of given type, or default value if null.
|
||||
/** Note that unless the function is instantiated with an explicit template
|
||||
* argument, the Default value's type also determines the result type.
|
||||
*/
|
||||
template<typename T> T as(T const &default_value) const
|
||||
{
|
||||
if (is_null())
|
||||
return default_value;
|
||||
else
|
||||
return from_string<T>(this->view());
|
||||
}
|
||||
|
||||
/// Return value as object of given type, or throw exception if null.
|
||||
/** Use as `as<std::optional<int>>()` or `as<my_untemplated_optional_t>()` as
|
||||
* an alternative to `get<int>()`; this is disabled for use with raw pointers
|
||||
* (other than C-strings) because storage for the value can't safely be
|
||||
* allocated here
|
||||
*/
|
||||
template<typename T> T as() const
|
||||
{
|
||||
if (is_null())
|
||||
{
|
||||
if constexpr (not nullness<T>::has_null)
|
||||
internal::throw_null_conversion(type_name<T>);
|
||||
else
|
||||
return nullness<T>::null();
|
||||
}
|
||||
else
|
||||
{
|
||||
return from_string<T>(this->view());
|
||||
}
|
||||
}
|
||||
|
||||
/// Return value wrapped in some optional type (empty for nulls).
|
||||
/** Use as `get<int>()` as before to obtain previous behavior, or specify
|
||||
* container type with `get<int, std::optional>()`
|
||||
*/
|
||||
template<typename T, template<typename> class O = std::optional>
|
||||
constexpr O<T> get() const
|
||||
{
|
||||
return as<O<T>>();
|
||||
}
|
||||
|
||||
// TODO: constexpr noexcept, once array_parser constructor gets those.
|
||||
/// Parse the field as an SQL array.
|
||||
/** Call the parser to retrieve values (and structure) from the array.
|
||||
*
|
||||
* Make sure the @ref result object stays alive until parsing is finished. If
|
||||
* you keep the @ref row of `field` object alive, it will keep the @ref
|
||||
* result object alive as well.
|
||||
*/
|
||||
array_parser as_array() const &
|
||||
{
|
||||
return array_parser{c_str(), m_home.m_encoding};
|
||||
}
|
||||
//@}
|
||||
|
||||
|
||||
protected:
|
||||
constexpr result const &home() const noexcept { return m_home; }
|
||||
constexpr result::size_type idx() const noexcept { return m_row; }
|
||||
constexpr row_size_type col() const noexcept { return m_col; }
|
||||
|
||||
// TODO: Create gates.
|
||||
friend class pqxx::result;
|
||||
friend class pqxx::row;
|
||||
field(
|
||||
result const &r, result_size_type row_num, row_size_type col_num) noexcept
|
||||
:
|
||||
m_col{col_num}, m_home{r}, m_row{row_num}
|
||||
{}
|
||||
|
||||
/**
|
||||
* You'd expect this to be unsigned, but due to the way reverse iterators
|
||||
* are related to regular iterators, it must be allowed to underflow to -1.
|
||||
*/
|
||||
row_size_type m_col;
|
||||
|
||||
private:
|
||||
result m_home;
|
||||
result::size_type m_row;
|
||||
};
|
||||
|
||||
|
||||
template<> inline bool field::to<std::string>(std::string &obj) const
|
||||
{
|
||||
bool const null{is_null()};
|
||||
if (not null)
|
||||
obj = std::string{view()};
|
||||
return not null;
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
inline bool field::to<std::string>(
|
||||
std::string &obj, std::string const &default_value) const
|
||||
{
|
||||
bool const null{is_null()};
|
||||
if (null)
|
||||
obj = default_value;
|
||||
else
|
||||
obj = std::string{view()};
|
||||
return not null;
|
||||
}
|
||||
|
||||
|
||||
/// Specialization: `to(char const *&)`.
|
||||
/** The buffer has the same lifetime as the data in this result (i.e. of this
|
||||
* result object, or the last remaining one copied from it etc.), so take care
|
||||
* not to use it after the last result object referring to this query result is
|
||||
* destroyed.
|
||||
*/
|
||||
template<> inline bool field::to<char const *>(char const *&obj) const
|
||||
{
|
||||
bool const null{is_null()};
|
||||
if (not null)
|
||||
obj = c_str();
|
||||
return not null;
|
||||
}
|
||||
|
||||
|
||||
template<> inline bool field::to<std::string_view>(std::string_view &obj) const
|
||||
{
|
||||
bool const null{is_null()};
|
||||
if (not null)
|
||||
obj = view();
|
||||
return not null;
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
inline bool field::to<std::string_view>(
|
||||
std::string_view &obj, std::string_view const &default_value) const
|
||||
{
|
||||
bool const null{is_null()};
|
||||
if (null)
|
||||
obj = default_value;
|
||||
else
|
||||
obj = view();
|
||||
return not null;
|
||||
}
|
||||
|
||||
|
||||
template<> inline std::string_view field::as<std::string_view>() const
|
||||
{
|
||||
if (is_null())
|
||||
PQXX_UNLIKELY
|
||||
internal::throw_null_conversion(type_name<std::string_view>);
|
||||
return view();
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
inline std::string_view
|
||||
field::as<std::string_view>(std::string_view const &default_value) const
|
||||
{
|
||||
return is_null() ? default_value : view();
|
||||
}
|
||||
|
||||
|
||||
template<> inline bool field::to<zview>(zview &obj) const
|
||||
{
|
||||
bool const null{is_null()};
|
||||
if (not null)
|
||||
obj = zview{c_str(), size()};
|
||||
return not null;
|
||||
}
|
||||
|
||||
|
||||
template<>
|
||||
inline bool field::to<zview>(zview &obj, zview const &default_value) const
|
||||
{
|
||||
bool const null{is_null()};
|
||||
if (null)
|
||||
obj = default_value;
|
||||
else
|
||||
obj = zview{c_str(), size()};
|
||||
return not null;
|
||||
}
|
||||
|
||||
|
||||
template<> inline zview field::as<zview>() const
|
||||
{
|
||||
if (is_null())
|
||||
PQXX_UNLIKELY
|
||||
internal::throw_null_conversion(type_name<zview>);
|
||||
return zview{c_str(), size()};
|
||||
}
|
||||
|
||||
|
||||
template<> inline zview field::as<zview>(zview const &default_value) const
|
||||
{
|
||||
return is_null() ? default_value : zview{c_str(), size()};
|
||||
}
|
||||
|
||||
|
||||
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
|
||||
class field_streambuf : public std::basic_streambuf<CHAR, TRAITS>
|
||||
{
|
||||
public:
|
||||
using char_type = CHAR;
|
||||
using traits_type = TRAITS;
|
||||
using int_type = typename traits_type::int_type;
|
||||
using pos_type = typename traits_type::pos_type;
|
||||
using off_type = typename traits_type::off_type;
|
||||
using openmode = std::ios::openmode;
|
||||
using seekdir = std::ios::seekdir;
|
||||
|
||||
explicit field_streambuf(field const &f) : m_field{f} { initialize(); }
|
||||
|
||||
protected:
|
||||
virtual int sync() override { return traits_type::eof(); }
|
||||
|
||||
virtual pos_type seekoff(off_type, seekdir, openmode) override
|
||||
{
|
||||
return traits_type::eof();
|
||||
}
|
||||
virtual pos_type seekpos(pos_type, openmode) override
|
||||
{
|
||||
return traits_type::eof();
|
||||
}
|
||||
virtual int_type overflow(int_type) override { return traits_type::eof(); }
|
||||
virtual int_type underflow() override { return traits_type::eof(); }
|
||||
|
||||
private:
|
||||
field const &m_field;
|
||||
|
||||
int_type initialize()
|
||||
{
|
||||
auto g{static_cast<char_type *>(const_cast<char *>(m_field.c_str()))};
|
||||
this->setg(g, g, g + std::size(m_field));
|
||||
return int_type(std::size(m_field));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// Input stream that gets its data from a result field
|
||||
/** Use this class exactly as you would any other istream to read data from a
|
||||
* field. All formatting and streaming operations of `std::istream` are
|
||||
* supported. What you'll typically want to use, however, is the fieldstream
|
||||
* alias (which defines a @ref basic_fieldstream for `char`). This is similar
|
||||
* to how e.g. `std::ifstream` relates to `std::basic_ifstream`.
|
||||
*
|
||||
* This class has only been tested for the char type (and its default traits).
|
||||
*/
|
||||
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
|
||||
class basic_fieldstream : public std::basic_istream<CHAR, TRAITS>
|
||||
{
|
||||
using super = std::basic_istream<CHAR, TRAITS>;
|
||||
|
||||
public:
|
||||
using char_type = CHAR;
|
||||
using traits_type = TRAITS;
|
||||
using int_type = typename traits_type::int_type;
|
||||
using pos_type = typename traits_type::pos_type;
|
||||
using off_type = typename traits_type::off_type;
|
||||
|
||||
basic_fieldstream(field const &f) : super{nullptr}, m_buf{f}
|
||||
{
|
||||
super::init(&m_buf);
|
||||
}
|
||||
|
||||
private:
|
||||
field_streambuf<CHAR, TRAITS> m_buf;
|
||||
};
|
||||
|
||||
using fieldstream = basic_fieldstream<char>;
|
||||
|
||||
/// Write a result field to any type of stream
|
||||
/** This can be convenient when writing a field to an output stream. More
|
||||
* importantly, it lets you write a field to e.g. a `stringstream` which you
|
||||
* can then use to read, format and convert the field in ways that to() does
|
||||
* not support.
|
||||
*
|
||||
* Example: parse a field into a variable of the nonstandard
|
||||
* "<tt>long long</tt>" type.
|
||||
*
|
||||
* ```cxx
|
||||
* extern result R;
|
||||
* long long L;
|
||||
* stringstream S;
|
||||
*
|
||||
* // Write field's string into S
|
||||
* S << R[0][0];
|
||||
*
|
||||
* // Parse contents of S into L
|
||||
* S >> L;
|
||||
* ```
|
||||
*/
|
||||
template<typename CHAR>
|
||||
inline std::basic_ostream<CHAR> &
|
||||
operator<<(std::basic_ostream<CHAR> &s, field const &value)
|
||||
{
|
||||
s.write(value.c_str(), std::streamsize(std::size(value)));
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
/// Convert a field's value to type `T`.
|
||||
/** Unlike the "regular" `from_string`, this knows how to deal with null
|
||||
* values.
|
||||
*/
|
||||
template<typename T> inline T from_string(field const &value)
|
||||
{
|
||||
if (value.is_null())
|
||||
{
|
||||
if constexpr (nullness<T>::has_null)
|
||||
return nullness<T>::null();
|
||||
else
|
||||
internal::throw_null_conversion(type_name<T>);
|
||||
}
|
||||
else
|
||||
{
|
||||
return from_string<T>(value.view());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Convert a field's value to `nullptr_t`.
|
||||
/** Yes, you read that right. This conversion does nothing useful. It always
|
||||
* returns `nullptr`.
|
||||
*
|
||||
* Except... what if the field is not null? In that case, this throws
|
||||
* @ref conversion_error.
|
||||
*/
|
||||
template<>
|
||||
inline std::nullptr_t from_string<std::nullptr_t>(field const &value)
|
||||
{
|
||||
if (not value.is_null())
|
||||
throw conversion_error{
|
||||
"Extracting non-null field into nullptr_t variable."};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
/// Convert a field to a string.
|
||||
template<> PQXX_LIBEXPORT std::string to_string(field const &value);
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,305 @@
|
||||
#if !defined(PQXX_ARRAY_COMPOSITE_HXX)
|
||||
# define PQXX_ARRAY_COMPOSITE_HXX
|
||||
|
||||
# include <cassert>
|
||||
|
||||
# include "pqxx/strconv.hxx"
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
// Find the end of a double-quoted string.
|
||||
/** `input[pos]` must be the opening double quote.
|
||||
*
|
||||
* Returns the offset of the first position after the closing quote.
|
||||
*/
|
||||
inline std::size_t scan_double_quoted_string(
|
||||
char const input[], std::size_t size, std::size_t pos,
|
||||
pqxx::internal::glyph_scanner_func *scan)
|
||||
{
|
||||
// XXX: find_char<'"', '\\'>().
|
||||
auto next{scan(input, size, pos)};
|
||||
bool at_quote{false};
|
||||
for (pos = next, next = scan(input, size, pos); pos < size;
|
||||
pos = next, next = scan(input, size, pos))
|
||||
{
|
||||
if (at_quote)
|
||||
{
|
||||
if (next - pos == 1 and input[pos] == '"')
|
||||
{
|
||||
// We just read a pair of double quotes. Carry on.
|
||||
at_quote = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We just read one double quote, and now we're at a character that's
|
||||
// not a second double quote. Ergo, that last character was the
|
||||
// closing double quote and this is the position right after it.
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
else if (next - pos == 1)
|
||||
{
|
||||
switch (input[pos])
|
||||
{
|
||||
case '\\':
|
||||
// Backslash escape. Skip ahead by one more character.
|
||||
pos = next;
|
||||
next = scan(input, size, pos);
|
||||
break;
|
||||
|
||||
case '"':
|
||||
// This is either the closing double quote, or the first of a pair of
|
||||
// double quotes.
|
||||
at_quote = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multibyte character. Carry on.
|
||||
}
|
||||
}
|
||||
if (not at_quote)
|
||||
throw argument_error{
|
||||
"Missing closing double-quote: " + std::string{input}};
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
/// Un-quote and un-escape a double-quoted SQL string.
|
||||
inline std::string parse_double_quoted_string(
|
||||
char const input[], std::size_t end, std::size_t pos,
|
||||
pqxx::internal::glyph_scanner_func *scan)
|
||||
{
|
||||
std::string output;
|
||||
// Maximum output size is same as the input size, minus the opening and
|
||||
// closing quotes. Or in the extreme opposite case, the real number could be
|
||||
// half that. Usually it'll be a pretty close estimate.
|
||||
output.reserve(std::size_t(end - pos - 2));
|
||||
|
||||
for (auto here{scan(input, end, pos)}, next{scan(input, end, here)};
|
||||
here < end - 1; here = next, next = scan(input, end, here))
|
||||
{
|
||||
// A backslash here is always an escape. So is a double-quote, since we're
|
||||
// inside the double-quoted string. In either case, we can just ignore the
|
||||
// escape character and use the next character. This is the one redeeming
|
||||
// feature of SQL's escaping system.
|
||||
if ((next - here == 1) and (input[here] == '\\' or input[here] == '"'))
|
||||
{
|
||||
// Skip escape.
|
||||
here = next;
|
||||
next = scan(input, end, here);
|
||||
}
|
||||
output.append(input + here, input + next);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/// Find the end of an unquoted string in an array or composite-type value.
|
||||
/** Stops when it gets to the end of the input; or when it sees any of the
|
||||
* characters in STOP which has not been escaped.
|
||||
*
|
||||
* For array values, STOP is a comma, a semicolon, or a closing brace. For
|
||||
* a value of a composite type, STOP is a comma or a closing parenthesis.
|
||||
*/
|
||||
template<char... STOP>
|
||||
inline std::size_t scan_unquoted_string(
|
||||
char const input[], std::size_t size, std::size_t pos,
|
||||
pqxx::internal::glyph_scanner_func *scan)
|
||||
{
|
||||
bool at_backslash{false};
|
||||
auto next{scan(input, size, pos)};
|
||||
while ((pos < size) and
|
||||
((next - pos) > 1 or at_backslash or ((input[pos] != STOP) and ...)))
|
||||
{
|
||||
pos = next;
|
||||
next = scan(input, size, pos);
|
||||
at_backslash =
|
||||
((not at_backslash) and ((next - pos) == 1) and (input[pos] == '\\'));
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
/// Parse an unquoted array entry or cfield of a composite-type field.
|
||||
inline std::string parse_unquoted_string(
|
||||
char const input[], std::size_t end, std::size_t pos,
|
||||
pqxx::internal::glyph_scanner_func *scan)
|
||||
{
|
||||
std::string output;
|
||||
bool at_backslash{false};
|
||||
output.reserve(end - pos);
|
||||
for (auto next{scan(input, end, pos)}; pos < end;
|
||||
pos = next, next = scan(input, end, pos))
|
||||
{
|
||||
at_backslash =
|
||||
((not at_backslash) and ((next - pos) == 1) and (input[pos] == '\\'));
|
||||
if (not at_backslash)
|
||||
output.append(input + pos, next - pos);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
/// Parse a field of a composite-type value.
|
||||
/** `T` is the C++ type of the field we're parsing, and `index` is its
|
||||
* zero-based number.
|
||||
*
|
||||
* Strip off the leading parenthesis or bracket yourself before parsing.
|
||||
* However, this function will parse the lcosing parenthesis or bracket.
|
||||
*
|
||||
* After a successful parse, `pos` will point at `std::end(text)`.
|
||||
*
|
||||
* For the purposes of parsing, ranges and arrays count as compositve values,
|
||||
* so this function supports parsing those. If you specifically need a closing
|
||||
* parenthesis, check afterwards that `text` did not end in a bracket instead.
|
||||
*
|
||||
* @param index Index of the current field, zero-based. It will increment for
|
||||
* the next field.
|
||||
* @param input Full input text for the entire composite-type value.
|
||||
* @param pos Starting position (in `input`) of the field that we're parsing.
|
||||
* After parsing, this will point at the beginning of the next field if
|
||||
* there is one, or one position past the last character otherwise.
|
||||
* @param field Destination for the parsed value.
|
||||
* @param scan Glyph scanning function for the relevant encoding type.
|
||||
* @param last_field Number of the last field in the value (zero-based). When
|
||||
* parsing the last field, this will equal `index`.
|
||||
*/
|
||||
template<typename T>
|
||||
inline void parse_composite_field(
|
||||
std::size_t &index, std::string_view input, std::size_t &pos, T &field,
|
||||
glyph_scanner_func *scan, std::size_t last_field)
|
||||
{
|
||||
assert(index <= last_field);
|
||||
auto next{scan(std::data(input), std::size(input), pos)};
|
||||
if ((next - pos) != 1)
|
||||
throw conversion_error{"Non-ASCII character in composite-type syntax."};
|
||||
|
||||
// Expect a field.
|
||||
switch (input[pos])
|
||||
{
|
||||
case ',':
|
||||
case ')':
|
||||
case ']':
|
||||
// The field is empty, i.e, null.
|
||||
if constexpr (nullness<T>::has_null)
|
||||
field = nullness<T>::null();
|
||||
else
|
||||
throw conversion_error{
|
||||
"Can't read composite field " + to_string(index) + ": C++ type " +
|
||||
type_name<T> + " does not support nulls."};
|
||||
break;
|
||||
|
||||
case '"': {
|
||||
auto const stop{scan_double_quoted_string(
|
||||
std::data(input), std::size(input), pos, scan)};
|
||||
auto const text{
|
||||
parse_double_quoted_string(std::data(input), stop, pos, scan)};
|
||||
field = from_string<T>(text);
|
||||
pos = stop;
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
auto const stop{scan_unquoted_string<',', ')', ']'>(
|
||||
std::data(input), std::size(input), pos, scan)};
|
||||
auto const text{parse_unquoted_string(std::data(input), stop, pos, scan)};
|
||||
field = from_string<T>(text);
|
||||
pos = stop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Expect a comma or a closing parenthesis.
|
||||
next = scan(std::data(input), std::size(input), pos);
|
||||
|
||||
if ((next - pos) != 1)
|
||||
throw conversion_error{
|
||||
"Unexpected non-ASCII character after composite field: " +
|
||||
std::string{input}};
|
||||
|
||||
if (index < last_field)
|
||||
{
|
||||
if (input[pos] != ',')
|
||||
throw conversion_error{
|
||||
"Found '" + std::string{input[pos]} +
|
||||
"' in composite value where comma was expected: " + std::data(input)};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (input[pos] == ',')
|
||||
throw conversion_error{
|
||||
"Composite value contained more fields than the expected " +
|
||||
to_string(last_field) + ": " + std::data(input)};
|
||||
if (input[pos] != ')' and input[pos] != ']')
|
||||
throw conversion_error{
|
||||
"Composite value has unexpected characters where closing parenthesis "
|
||||
"was expected: " +
|
||||
std::string{input}};
|
||||
if (next != std::size(input))
|
||||
throw conversion_error{
|
||||
"Composite value has unexpected text after closing parenthesis: " +
|
||||
std::string{input}};
|
||||
}
|
||||
|
||||
pos = next;
|
||||
++index;
|
||||
}
|
||||
|
||||
|
||||
/// Conservatively estimate buffer size needed for a composite field.
|
||||
template<typename T>
|
||||
inline std::size_t size_composite_field_buffer(T const &field)
|
||||
{
|
||||
if constexpr (is_unquoted_safe<T>)
|
||||
{
|
||||
// Safe to copy, without quotes or escaping. Drop the terminating zero.
|
||||
return size_buffer(field) - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// + Opening quote.
|
||||
// + Field budget.
|
||||
// - Terminating zero.
|
||||
// + Escaping for each byte in the field's string representation.
|
||||
// - Escaping for terminating zero.
|
||||
// + Closing quote.
|
||||
return 1 + 2 * (size_buffer(field) - 1) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
inline void write_composite_field(char *&pos, char *end, T const &field)
|
||||
{
|
||||
if constexpr (is_unquoted_safe<T>)
|
||||
{
|
||||
// No need for quoting or escaping. Convert it straight into its final
|
||||
// place in the buffer, and "backspace" the trailing zero.
|
||||
pos = string_traits<T>::into_buf(pos, end, field) - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The field may need escaping, which means we need an intermediate buffer.
|
||||
// To avoid allocating that at run time, we use the end of the buffer that
|
||||
// we have.
|
||||
auto const budget{size_buffer(field)};
|
||||
*pos++ = '"';
|
||||
|
||||
// Now escape buf into its final position.
|
||||
for (char const c : string_traits<T>::to_buf(end - budget, end, field))
|
||||
{
|
||||
if ((c == '"') or (c == '\\'))
|
||||
*pos++ = '\\';
|
||||
|
||||
*pos++ = c;
|
||||
}
|
||||
|
||||
*pos++ = '"';
|
||||
}
|
||||
|
||||
*pos++ = ',';
|
||||
}
|
||||
} // namespace pqxx::internal
|
||||
#endif
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef PQXX_H_CALLGATE
|
||||
#define PQXX_H_CALLGATE
|
||||
|
||||
/*
|
||||
Here's what a typical gate class definition looks like:
|
||||
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE @gateclass@ : callgate<@host@>
|
||||
{
|
||||
friend class @client@;
|
||||
|
||||
@gateclass@(reference x) : super(x) {}
|
||||
|
||||
// Methods here. Use home() to access the host-class object.
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
*/
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
/// Base class for call gates.
|
||||
/**
|
||||
* A call gate defines a limited, private interface on the host class that
|
||||
* specified client classes can access.
|
||||
*
|
||||
* The metaphor works as follows: the gate stands in front of a "home," which
|
||||
* is really a class, and only lets specific friends in.
|
||||
*
|
||||
* To implement a call gate that gives client C access to host H,
|
||||
* * derive a gate class from callgate<H>;
|
||||
* * make the gate class a friend of H;
|
||||
* * make C a friend of the gate class; and
|
||||
* * implement "stuff C can do with H" as private members in the gate class.
|
||||
*
|
||||
* This special kind of "gated" friendship gives C private access to H, but
|
||||
* only through an expressly limited interface. The gate class can access its
|
||||
* host object as home().
|
||||
*
|
||||
* Keep gate classes entirely stateless. They should be ultra-lightweight
|
||||
* wrappers for their host classes, and be optimized away as much as possible
|
||||
* by the compiler. Once you start adding state, you're on a slippery slope
|
||||
* away from the pure, clean, limited interface pattern that gate classes are
|
||||
* meant to implement.
|
||||
*
|
||||
* Ideally, all member functions of the gate class should be one-liners passing
|
||||
* calls straight on to the host class. It can be useful however to break this
|
||||
* rule temporarily during inter-class refactoring.
|
||||
*/
|
||||
template<typename HOME> class PQXX_PRIVATE callgate
|
||||
{
|
||||
protected:
|
||||
/// This class, to keep constructors easy.
|
||||
using super = callgate<HOME>;
|
||||
/// A reference to the host class. Helps keep constructors easy.
|
||||
using reference = HOME &;
|
||||
|
||||
callgate(reference x) : m_home(x) {}
|
||||
|
||||
/// The home object. The gate class has full "private" access.
|
||||
reference home() const noexcept { return m_home; }
|
||||
|
||||
private:
|
||||
reference m_home;
|
||||
};
|
||||
} // namespace pqxx::internal
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#if !defined(PQXX_CONCAT_HXX)
|
||||
# define PQXX_CONCAT_HXX
|
||||
|
||||
# include <string>
|
||||
# include <string_view>
|
||||
|
||||
# include "pqxx/strconv.hxx"
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
/// Convert item to a string, write it into [here, end).
|
||||
template<typename TYPE>
|
||||
void render_item(TYPE const &item, char *&here, char *end)
|
||||
{
|
||||
here = string_traits<TYPE>::into_buf(here, end, item) - 1;
|
||||
}
|
||||
|
||||
|
||||
// C++20: Support non-random_access_range ranges.
|
||||
/// Efficiently combine a bunch of items into one big string.
|
||||
/** Use this as an optimised version of string concatentation. It takes just
|
||||
* about any type; it will represent each item as a string according to its
|
||||
* @ref string_traits.
|
||||
*
|
||||
* This is a simpler, more specialised version of @ref separated_list for a
|
||||
* statically known series of items, possibly of different types.
|
||||
*/
|
||||
template<typename... TYPE>
|
||||
[[nodiscard]] inline std::string concat(TYPE... item)
|
||||
{
|
||||
std::string buf;
|
||||
// Size to accommodate string representations of all inputs, minus their
|
||||
// terminating zero bytes.
|
||||
buf.resize(size_buffer(item...));
|
||||
|
||||
char *const data{buf.data()};
|
||||
char *here = data;
|
||||
char *end = data + std::size(buf);
|
||||
(render_item(item, here, end), ...);
|
||||
|
||||
buf.resize(static_cast<std::size_t>(here - data));
|
||||
return buf;
|
||||
}
|
||||
} // namespace pqxx::internal
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
/** Enum type for supporting encodings in libpqxx
|
||||
*
|
||||
* 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_ENCODING_GROUP
|
||||
#define PQXX_H_ENCODING_GROUP
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
// Types of encodings supported by PostgreSQL, see
|
||||
// https://www.postgresql.org/docs/current/static/multibyte.html#CHARSET-TABLE
|
||||
enum class encoding_group
|
||||
{
|
||||
// Handles all single-byte fixed-width encodings
|
||||
MONOBYTE,
|
||||
|
||||
// Multibyte encodings.
|
||||
// Many of these can embed ASCII-like bytes inside multibyte characters,
|
||||
// notably Big5, SJIS, SHIFT_JIS_2004, GP18030, GBK, JOHAB, UHC.
|
||||
BIG5,
|
||||
EUC_CN,
|
||||
// TODO: Merge EUC_JP and EUC_JIS_2004?
|
||||
EUC_JP,
|
||||
EUC_JIS_2004,
|
||||
EUC_KR,
|
||||
EUC_TW,
|
||||
GB18030,
|
||||
GBK,
|
||||
JOHAB,
|
||||
MULE_INTERNAL,
|
||||
// TODO: Merge SJIS and SHIFT_JIS_2004?
|
||||
SJIS,
|
||||
SHIFT_JIS_2004,
|
||||
UHC,
|
||||
UTF8,
|
||||
};
|
||||
|
||||
|
||||
// TODO:: Can we just use string_view now?
|
||||
/// Function type: "find the end of the current glyph."
|
||||
/** This type of function takes a text buffer, and a location in that buffer,
|
||||
* and returns the location one byte past the end of the current glyph.
|
||||
*
|
||||
* The start offset marks the beginning of the current glyph. It must fall
|
||||
* within the buffer.
|
||||
*
|
||||
* There are multiple different glyph scanner implementations, for different
|
||||
* kinds of encodings.
|
||||
*/
|
||||
using glyph_scanner_func =
|
||||
std::size_t(char const buffer[], std::size_t buffer_len, std::size_t start);
|
||||
} // namespace pqxx::internal
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,90 @@
|
||||
/** Internal string encodings support for libpqxx
|
||||
*
|
||||
* 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_ENCODINGS
|
||||
#define PQXX_H_ENCODINGS
|
||||
|
||||
#include "pqxx/internal/encoding_group.hxx"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
char const *name_encoding(int encoding_id);
|
||||
|
||||
/// Convert libpq encoding enum or encoding name to its libpqxx group.
|
||||
encoding_group enc_group(int /* libpq encoding ID */);
|
||||
encoding_group enc_group(std::string_view);
|
||||
|
||||
|
||||
/// Look up the glyph scanner function for a given encoding group.
|
||||
/** To identify the glyph boundaries in a buffer, call this to obtain the
|
||||
* scanner function appropriate for the buffer's encoding. Then, repeatedly
|
||||
* call the scanner function to find the glyphs.
|
||||
*/
|
||||
PQXX_LIBEXPORT glyph_scanner_func *get_glyph_scanner(encoding_group);
|
||||
|
||||
|
||||
// TODO: For ASCII search, treat UTF8/EUC_*/MULE_INTERNAL as MONOBYTE.
|
||||
|
||||
/// Find any of the ASCII characters `NEEDLE` in `haystack`.
|
||||
/** Scans through `haystack` until it finds a single-byte character that
|
||||
* matches any value in `NEEDLE`.
|
||||
*
|
||||
* If it finds one, returns its offset. If not, returns the end of the
|
||||
* haystack.
|
||||
*/
|
||||
template<char... NEEDLE>
|
||||
inline std::size_t find_char(
|
||||
glyph_scanner_func *scanner, std::string_view haystack,
|
||||
std::size_t here = 0u)
|
||||
{
|
||||
auto const sz{std::size(haystack)};
|
||||
auto const data{std::data(haystack)};
|
||||
while (here < sz)
|
||||
{
|
||||
auto next{scanner(data, sz, here)};
|
||||
// (For some reason gcc had a problem with a right-fold here. But clang
|
||||
// was fine.)
|
||||
if ((... or (data[here] == NEEDLE)))
|
||||
{
|
||||
// Also check against a multibyte character starting with a bytes which
|
||||
// just happens to match one of the ASCII bytes we're looking for. It'd
|
||||
// be cleaner to check that first, but either works. So, let's apply the
|
||||
// most selective filter first and skip this check in almost all cases.
|
||||
if (next == here + 1)
|
||||
return here;
|
||||
}
|
||||
|
||||
// Nope, no hit. Move on.
|
||||
here = next;
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
|
||||
/// Iterate over the glyphs in a buffer.
|
||||
/** Scans the glyphs in the buffer, and for each, passes its begin and its
|
||||
* one-past-end pointers to `callback`.
|
||||
*/
|
||||
template<typename CALLABLE>
|
||||
inline void for_glyphs(
|
||||
encoding_group enc, CALLABLE callback, char const buffer[],
|
||||
std::size_t buffer_len, std::size_t start = 0)
|
||||
{
|
||||
auto const scan{get_glyph_scanner(enc)};
|
||||
for (std::size_t here = start, next; here < buffer_len; here = next)
|
||||
{
|
||||
next = scan(buffer, buffer_len, here);
|
||||
callback(buffer + here, buffer + next);
|
||||
}
|
||||
}
|
||||
} // namespace pqxx::internal
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
class connection;
|
||||
class errorhandler;
|
||||
} // namespace pqxx
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE connection_errorhandler : callgate<connection>
|
||||
{
|
||||
friend class pqxx::errorhandler;
|
||||
|
||||
connection_errorhandler(reference x) : super(x) {}
|
||||
|
||||
void register_errorhandler(errorhandler *h)
|
||||
{
|
||||
home().register_errorhandler(h);
|
||||
}
|
||||
void unregister_errorhandler(errorhandler *h)
|
||||
{
|
||||
home().unregister_errorhandler(h);
|
||||
}
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,35 @@
|
||||
#include <string>
|
||||
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
#include <pqxx/internal/libpq-forward.hxx>
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
class blob;
|
||||
class largeobject;
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE connection_largeobject : callgate<connection>
|
||||
{
|
||||
friend class pqxx::blob;
|
||||
friend class pqxx::largeobject;
|
||||
|
||||
connection_largeobject(reference x) : super(x) {}
|
||||
|
||||
pq::PGconn *raw_connection() const { return home().raw_connection(); }
|
||||
};
|
||||
|
||||
|
||||
class PQXX_PRIVATE const_connection_largeobject : callgate<connection const>
|
||||
{
|
||||
friend class pqxx::blob;
|
||||
friend class pqxx::largeobject;
|
||||
|
||||
const_connection_largeobject(reference x) : super(x) {}
|
||||
|
||||
std::string error_message() const { return home().err_msg(); }
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
#include "pqxx/connection.hxx"
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
class notification_receiver;
|
||||
}
|
||||
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE connection_notification_receiver : callgate<connection>
|
||||
{
|
||||
friend class pqxx::notification_receiver;
|
||||
|
||||
connection_notification_receiver(reference x) : super(x) {}
|
||||
|
||||
void add_receiver(notification_receiver *receiver)
|
||||
{
|
||||
home().add_receiver(receiver);
|
||||
}
|
||||
void remove_receiver(notification_receiver *receiver) noexcept
|
||||
{
|
||||
home().remove_receiver(receiver);
|
||||
}
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "pqxx/internal/libpq-forward.hxx"
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
#include "pqxx/pipeline.hxx"
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE connection_pipeline : callgate<connection>
|
||||
{
|
||||
friend class pqxx::pipeline;
|
||||
|
||||
connection_pipeline(reference x) : super(x) {}
|
||||
|
||||
void start_exec(char const query[]) { home().start_exec(query); }
|
||||
pqxx::internal::pq::PGresult *get_result() { return home().get_result(); }
|
||||
void cancel_query() { home().cancel_query(); }
|
||||
|
||||
bool consume_input() noexcept { return home().consume_input(); }
|
||||
bool is_busy() const noexcept { return home().is_busy(); }
|
||||
|
||||
int encoding_id() { return home().encoding_id(); }
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,19 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
class sql_cursor;
|
||||
}
|
||||
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE connection_sql_cursor : callgate<connection>
|
||||
{
|
||||
friend class pqxx::internal::sql_cursor;
|
||||
|
||||
connection_sql_cursor(reference x) : super(x) {}
|
||||
|
||||
result exec(char const query[]) { return home().exec(query); }
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
#include "pqxx/connection.hxx"
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE connection_stream_from : callgate<connection>
|
||||
{
|
||||
friend class pqxx::stream_from;
|
||||
|
||||
connection_stream_from(reference x) : super{x} {}
|
||||
|
||||
auto read_copy_line() { return home().read_copy_line(); }
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
#include "pqxx/stream_to.hxx"
|
||||
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE connection_stream_to : callgate<connection>
|
||||
{
|
||||
friend class pqxx::stream_to;
|
||||
|
||||
connection_stream_to(reference x) : super(x) {}
|
||||
|
||||
void write_copy_line(std::string_view line) { home().write_copy_line(line); }
|
||||
void end_copy_write() { home().end_copy_write(); }
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
class connection;
|
||||
}
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE connection_transaction : callgate<connection>
|
||||
{
|
||||
friend class pqxx::transaction_base;
|
||||
|
||||
connection_transaction(reference x) : super(x) {}
|
||||
|
||||
template<typename STRING> result exec(STRING query, std::string_view desc)
|
||||
{
|
||||
return home().exec(query, desc);
|
||||
}
|
||||
|
||||
void register_transaction(transaction_base *t)
|
||||
{
|
||||
home().register_transaction(t);
|
||||
}
|
||||
void unregister_transaction(transaction_base *t) noexcept
|
||||
{
|
||||
home().unregister_transaction(t);
|
||||
}
|
||||
|
||||
auto read_copy_line() { return home().read_copy_line(); }
|
||||
void write_copy_line(std::string_view line) { home().write_copy_line(line); }
|
||||
void end_copy_write() { home().end_copy_write(); }
|
||||
|
||||
result exec_prepared(zview statement, internal::c_params const &args)
|
||||
{
|
||||
return home().exec_prepared(statement, args);
|
||||
}
|
||||
|
||||
result exec_params(zview query, internal::c_params const &args)
|
||||
{
|
||||
return home().exec_params(query, args);
|
||||
}
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE errorhandler_connection : callgate<errorhandler>
|
||||
{
|
||||
friend class pqxx::connection;
|
||||
|
||||
errorhandler_connection(reference x) : super(x) {}
|
||||
|
||||
void unregister() noexcept { home().unregister(); }
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE icursor_iterator_icursorstream : callgate<icursor_iterator>
|
||||
{
|
||||
friend class pqxx::icursorstream;
|
||||
|
||||
icursor_iterator_icursorstream(reference x) : super(x) {}
|
||||
|
||||
icursor_iterator::difference_type pos() const noexcept
|
||||
{
|
||||
return home().pos();
|
||||
}
|
||||
|
||||
icursor_iterator *get_prev() { return home().m_prev; }
|
||||
void set_prev(icursor_iterator *i) { home().m_prev = i; }
|
||||
|
||||
icursor_iterator *get_next() { return home().m_next; }
|
||||
void set_next(icursor_iterator *i) { home().m_next = i; }
|
||||
|
||||
void fill(result const &r) { home().fill(r); }
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE icursorstream_icursor_iterator : callgate<icursorstream>
|
||||
{
|
||||
friend class pqxx::icursor_iterator;
|
||||
|
||||
icursorstream_icursor_iterator(reference x) : super(x) {}
|
||||
|
||||
void insert_iterator(icursor_iterator *i) noexcept
|
||||
{
|
||||
home().insert_iterator(i);
|
||||
}
|
||||
|
||||
void remove_iterator(icursor_iterator *i) const noexcept
|
||||
{
|
||||
home().remove_iterator(i);
|
||||
}
|
||||
|
||||
icursorstream::size_type forward() { return home().forward(); }
|
||||
icursorstream::size_type forward(icursorstream::size_type n)
|
||||
{
|
||||
return home().forward(n);
|
||||
}
|
||||
|
||||
void service_iterators(icursorstream::difference_type p)
|
||||
{
|
||||
home().service_iterators(p);
|
||||
}
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,14 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE result_connection : callgate<result const>
|
||||
{
|
||||
friend class pqxx::connection;
|
||||
|
||||
result_connection(reference x) : super(x) {}
|
||||
|
||||
operator bool() const { return bool(home()); }
|
||||
bool operator!() const { return not home(); }
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE result_creation : callgate<result const>
|
||||
{
|
||||
friend class pqxx::connection;
|
||||
friend class pqxx::pipeline;
|
||||
|
||||
result_creation(reference x) : super(x) {}
|
||||
|
||||
static result create(
|
||||
internal::pq::PGresult *rhs, std::shared_ptr<std::string> const &query,
|
||||
encoding_group enc)
|
||||
{
|
||||
return result(rhs, query, enc);
|
||||
}
|
||||
|
||||
void check_status(std::string_view desc = ""sv) const
|
||||
{
|
||||
return home().check_status(desc);
|
||||
}
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,16 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE result_pipeline : callgate<result const>
|
||||
{
|
||||
friend class pqxx::pipeline;
|
||||
|
||||
result_pipeline(reference x) : super(x) {}
|
||||
|
||||
std::shared_ptr<std::string const> query_ptr() const
|
||||
{
|
||||
return home().query_ptr();
|
||||
}
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE result_sql_cursor : callgate<result const>
|
||||
{
|
||||
friend class pqxx::internal::sql_cursor;
|
||||
|
||||
result_sql_cursor(reference x) : super(x) {}
|
||||
|
||||
char const *cmd_status() const noexcept { return home().cmd_status(); }
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,10 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE transaction_sql_cursor : callgate<transaction_base>
|
||||
{
|
||||
friend class pqxx::internal::sql_cursor;
|
||||
transaction_sql_cursor(reference x) : super(x) {}
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <pqxx/internal/callgate.hxx>
|
||||
|
||||
#include "pqxx/transaction_base.hxx"
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class PQXX_PRIVATE transaction_transaction_focus : callgate<transaction_base>
|
||||
{
|
||||
friend class pqxx::transaction_focus;
|
||||
|
||||
transaction_transaction_focus(reference x) : super(x) {}
|
||||
|
||||
void register_focus(transaction_focus *focus)
|
||||
{
|
||||
home().register_focus(focus);
|
||||
}
|
||||
void unregister_focus(transaction_focus *focus) noexcept
|
||||
{
|
||||
home().unregister_focus(focus);
|
||||
}
|
||||
void register_pending_error(zview error)
|
||||
{
|
||||
home().register_pending_error(error);
|
||||
}
|
||||
void register_pending_error(std::string &&error)
|
||||
{
|
||||
home().register_pending_error(std::move(error));
|
||||
}
|
||||
};
|
||||
} // namespace pqxx::internal::gate
|
||||
@@ -0,0 +1,22 @@
|
||||
/* Compiler deficiency workarounds for compiling libpqxx headers.
|
||||
*
|
||||
* To be included at the end of each libpqxx header, in order to restore the
|
||||
* client program's settings.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
// NO GUARDS HERE! This code should be executed every time!
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# pragma warning(pop) // Restore compiler's warning state
|
||||
#endif
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include pqxx/internal/header-post.hxx AFTER its 'pre' counterpart."
|
||||
#endif
|
||||
|
||||
#undef PQXX_HEADER_PRE
|
||||
@@ -0,0 +1,169 @@
|
||||
/* Compiler settings for compiling libpqxx headers, and workarounds for all.
|
||||
*
|
||||
* Include this before including any other libpqxx headers from within libpqxx.
|
||||
* And to balance it out, also include header-post.hxx at the end of the batch
|
||||
* of headers.
|
||||
*
|
||||
* The public libpqxx headers (e.g. `<pqxx/connection>`) include this already;
|
||||
* there's no need to do this from within an application.
|
||||
*
|
||||
* Include this file at the highest aggregation level possible to avoid nesting
|
||||
* and to keep things simple.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// NO GUARD HERE! This part should be included every time this file is.
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
// Save compiler's warning state, and set warning level 4 for maximum
|
||||
// sensitivity to warnings.
|
||||
# pragma warning(push, 4)
|
||||
|
||||
// Visual C++ generates some entirely unreasonable warnings. Disable them.
|
||||
// Copy constructor could not be generated.
|
||||
# pragma warning(disable : 4511)
|
||||
// Assignment operator could not be generated.
|
||||
# pragma warning(disable : 4512)
|
||||
// Can't expose outside classes without exporting them. Except the MSVC docs
|
||||
// say please ignore the warning if it's a standard library class.
|
||||
# pragma warning(disable : 4251)
|
||||
// Can't derive library classes from outside classes without exporting them.
|
||||
// Except the MSVC docs say please ignore the warning if the parent class is
|
||||
// in the standard library.
|
||||
# pragma warning(disable : 4275)
|
||||
// Can't inherit from non-exported class.
|
||||
# pragma warning(disable : 4275)
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
|
||||
#if defined(PQXX_HEADER_PRE)
|
||||
# error "Avoid nesting #include of pqxx/internal/header-pre.hxx."
|
||||
#endif
|
||||
|
||||
#define PQXX_HEADER_PRE
|
||||
|
||||
|
||||
// Workarounds & definitions that need to be included even in library's headers
|
||||
#include "pqxx/config-public-compiler.h"
|
||||
|
||||
// Enable ISO-646 alternative operaotr representations: "and" instead of "&&"
|
||||
// etc. on older compilers. C++20 removes this header.
|
||||
#if __has_include(<ciso646>)
|
||||
# include <ciso646>
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(PQXX_HAVE_GCC_PURE)
|
||||
/// Declare function "pure": no side effects, only reads globals and its args.
|
||||
# define PQXX_PURE __attribute__((pure))
|
||||
#else
|
||||
# define PQXX_PURE /* pure */
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__GNUC__)
|
||||
/// Tell the compiler to optimise a function for size, not speed.
|
||||
# define PQXX_COLD __attribute__((cold))
|
||||
#else
|
||||
# define PQXX_COLD /* cold */
|
||||
#endif
|
||||
|
||||
|
||||
// Workarounds for Windows
|
||||
#ifdef _WIN32
|
||||
|
||||
/* For now, export DLL symbols if _DLL is defined. This is done automatically
|
||||
* by the compiler when linking to the dynamic version of the runtime library,
|
||||
* according to "gzh"
|
||||
*/
|
||||
# if defined(PQXX_SHARED) && !defined(PQXX_LIBEXPORT)
|
||||
# define PQXX_LIBEXPORT __declspec(dllimport)
|
||||
# endif // PQXX_SHARED && !PQXX_LIBEXPORT
|
||||
|
||||
|
||||
// Workarounds for Microsoft Visual C++
|
||||
# ifdef _MSC_VER
|
||||
|
||||
// Suppress vtables on abstract classes.
|
||||
# define PQXX_NOVTABLE __declspec(novtable)
|
||||
|
||||
// Automatically link with the appropriate libpq (static or dynamic, debug or
|
||||
// release). The default is to use the release DLL. Define PQXX_PQ_STATIC to
|
||||
// link to a static version of libpq, and _DEBUG to link to a debug version.
|
||||
// The two may be combined.
|
||||
# if defined(PQXX_AUTOLINK)
|
||||
# if defined(PQXX_PQ_STATIC)
|
||||
# ifdef _DEBUG
|
||||
# pragma comment(lib, "libpqd")
|
||||
# else
|
||||
# pragma comment(lib, "libpq")
|
||||
# endif
|
||||
# else
|
||||
# ifdef _DEBUG
|
||||
# pragma comment(lib, "libpqddll")
|
||||
# else
|
||||
# pragma comment(lib, "libpqdll")
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
|
||||
// If we're not compiling libpqxx itself, automatically link with the
|
||||
// appropriate libpqxx library. To link with the libpqxx DLL, define
|
||||
// PQXX_SHARED; the default is to link with the static library. A static link
|
||||
// is the recommended practice.
|
||||
//
|
||||
// The preprocessor macro PQXX_INTERNAL is used to detect whether we
|
||||
// are compiling the libpqxx library itself. When you compile the library
|
||||
// yourself using your own project file, make sure to include this macro.
|
||||
# if defined(PQXX_AUTOLINK) && !defined(PQXX_INTERNAL)
|
||||
# ifdef PQXX_SHARED
|
||||
# ifdef _DEBUG
|
||||
# pragma comment(lib, "libpqxxD")
|
||||
# else
|
||||
# pragma comment(lib, "libpqxx")
|
||||
# endif
|
||||
# else // !PQXX_SHARED
|
||||
# ifdef _DEBUG
|
||||
# pragma comment(lib, "libpqxx_staticD")
|
||||
# else
|
||||
# pragma comment(lib, "libpqxx_static")
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# endif // _MSC_VER
|
||||
|
||||
#elif defined(PQXX_HAVE_GCC_VISIBILITY) // !_WIN32
|
||||
|
||||
# define PQXX_LIBEXPORT __attribute__((visibility("default")))
|
||||
# define PQXX_PRIVATE __attribute__((visibility("hidden")))
|
||||
|
||||
#endif // PQXX_HAVE_GCC_VISIBILITY
|
||||
|
||||
|
||||
#ifndef PQXX_LIBEXPORT
|
||||
# define PQXX_LIBEXPORT /* libexport */
|
||||
#endif
|
||||
|
||||
#ifndef PQXX_PRIVATE
|
||||
# define PQXX_PRIVATE /* private */
|
||||
#endif
|
||||
|
||||
#ifndef PQXX_NOVTABLE
|
||||
# define PQXX_NOVTABLE /* novtable */
|
||||
#endif
|
||||
|
||||
// C++20: Assume support.
|
||||
#if defined(PQXX_HAVE_LIKELY)
|
||||
# define PQXX_LIKELY [[likely]]
|
||||
# define PQXX_UNLIKELY [[unlikely]]
|
||||
#else
|
||||
# define PQXX_LIKELY /* [[likely]] */
|
||||
# define PQXX_UNLIKELY /* [[unlikely]] */
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
/// End a code block started by "ignore-deprecated-pre.hxx".
|
||||
|
||||
#if !defined(PQXX_IGNORING_DEPRECATED)
|
||||
# error "Ended an 'ignore-deprecated' block while none was active."
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
# pragma GCC diagnostic pop
|
||||
#endif // __GNUC__
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#undef PQXX_IGNORING_DEPRECATED
|
||||
@@ -0,0 +1,28 @@
|
||||
/** Start a block of deprecated code which may call other deprecated code.
|
||||
*
|
||||
* Most compilers will emit warnings when deprecated code is invoked from
|
||||
* non-deprecated code. But some compilers (notably gcc) will always emit the
|
||||
* warning even when the calling code is also deprecated.
|
||||
*
|
||||
* This header starts a block where those warnings are suppressed. It can be
|
||||
* included inside a code block.
|
||||
*
|
||||
* Always match the #include with a closing #include of
|
||||
* "ignore-deprecated-post.hxx". To avoid mistakes, keep the enclosed area as
|
||||
* small as possible.
|
||||
*/
|
||||
#if defined(PQXX_IGNORING_DEPRECATED)
|
||||
# error "Started an 'ignore-deprecated' block inside another."
|
||||
#endif
|
||||
|
||||
#define PQXX_IGNORING_DEPRECATED
|
||||
|
||||
#if defined(__GNUC__)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif // __GNUC__
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable : 4996)
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
/** Minimal forward declarations of libpq types needed in libpqxx headers.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE when building client programs.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
extern "C"
|
||||
{
|
||||
struct pg_conn;
|
||||
struct pg_result;
|
||||
struct pgNotify;
|
||||
}
|
||||
|
||||
/// Forward declarations of libpq types as needed in libpqxx headers.
|
||||
namespace pqxx::internal::pq
|
||||
{
|
||||
using PGconn = pg_conn;
|
||||
using PGresult = pg_result;
|
||||
using PGnotify = pgNotify;
|
||||
using PQnoticeProcessor = void (*)(void *, char const *);
|
||||
} // namespace pqxx::internal::pq
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// PostgreSQL database row identifier.
|
||||
using oid = unsigned int;
|
||||
} // namespace pqxx
|
||||
@@ -0,0 +1,124 @@
|
||||
/** Result loops.
|
||||
*
|
||||
* 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_RESULT_ITER
|
||||
#define PQXX_H_RESULT_ITER
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "pqxx/strconv.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
class result;
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
// C++20: Replace with generator?
|
||||
/// Iterator for looped unpacking of a result.
|
||||
template<typename... TYPE> class result_iter
|
||||
{
|
||||
public:
|
||||
using value_type = std::tuple<TYPE...>;
|
||||
|
||||
/// Construct an "end" iterator.
|
||||
result_iter() = default;
|
||||
|
||||
explicit result_iter(result const &home) :
|
||||
m_home{&home}, m_size{std::size(home)}
|
||||
{
|
||||
if (not std::empty(home))
|
||||
read();
|
||||
}
|
||||
result_iter(result_iter const &) = default;
|
||||
|
||||
result_iter &operator++()
|
||||
{
|
||||
m_index++;
|
||||
if (m_index >= m_size)
|
||||
m_home = nullptr;
|
||||
else
|
||||
read();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Comparison only works for comparing to end().
|
||||
bool operator==(result_iter const &rhs) const
|
||||
{
|
||||
return m_home == rhs.m_home;
|
||||
}
|
||||
bool operator!=(result_iter const &rhs) const { return not(*this == rhs); }
|
||||
|
||||
value_type const &operator*() const { return m_value; }
|
||||
|
||||
private:
|
||||
void read() { (*m_home)[m_index].convert(m_value); }
|
||||
|
||||
result const *m_home{nullptr};
|
||||
result::size_type m_index{0};
|
||||
result::size_type m_size;
|
||||
value_type m_value;
|
||||
};
|
||||
|
||||
|
||||
template<typename... TYPE> class result_iteration
|
||||
{
|
||||
public:
|
||||
using iterator = result_iter<TYPE...>;
|
||||
explicit result_iteration(result const &home) : m_home{home}
|
||||
{
|
||||
constexpr auto tup_size{sizeof...(TYPE)};
|
||||
if (home.columns() != tup_size)
|
||||
throw usage_error{internal::concat(
|
||||
"Tried to extract ", to_string(tup_size),
|
||||
" field(s) from a result with ", to_string(home.columns()),
|
||||
" column(s).")};
|
||||
}
|
||||
iterator begin() const
|
||||
{
|
||||
if (std::size(m_home) == 0)
|
||||
return end();
|
||||
else
|
||||
return iterator{m_home};
|
||||
}
|
||||
iterator end() const { return {}; }
|
||||
|
||||
private:
|
||||
pqxx::result const &m_home;
|
||||
};
|
||||
} // namespace pqxx::internal
|
||||
|
||||
|
||||
template<typename... TYPE> inline auto pqxx::result::iter() const
|
||||
{
|
||||
return pqxx::internal::result_iteration<TYPE...>{*this};
|
||||
}
|
||||
|
||||
|
||||
template<typename CALLABLE>
|
||||
inline void pqxx::result::for_each(CALLABLE &&func) const
|
||||
{
|
||||
using args_tuple = internal::args_t<decltype(func)>;
|
||||
constexpr auto sz{std::tuple_size_v<args_tuple>};
|
||||
static_assert(
|
||||
sz > 0,
|
||||
"Callback for for_each must take parameters, one for each column in the "
|
||||
"result.");
|
||||
|
||||
auto const cols{this->columns()};
|
||||
if (sz != cols)
|
||||
throw usage_error{internal::concat(
|
||||
"Callback to for_each takes ", sz, "parameter", (sz == 1) ? "" : "s",
|
||||
", but result set has ", cols, "field", (cols == 1) ? "" : "s", ".")};
|
||||
|
||||
using pass_tuple = pqxx::internal::strip_types_t<args_tuple>;
|
||||
for (auto const r : *this) std::apply(func, r.as_tuple<pass_tuple>());
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,389 @@
|
||||
/* Definitions for the pqxx::result class and support classes.
|
||||
*
|
||||
* pqxx::result represents the set of result rows from a database query.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result 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_RESULT_ITERATOR
|
||||
#define PQXX_H_RESULT_ITERATOR
|
||||
|
||||
#include "pqxx/row.hxx"
|
||||
|
||||
|
||||
/* Result iterator.
|
||||
*
|
||||
* Don't include this header from your own application; it is included for you
|
||||
* by other libpqxx headers.
|
||||
*/
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Iterator for rows in a result. Use as result::const_iterator.
|
||||
/** A result, once obtained, cannot be modified. Therefore there is no
|
||||
* plain iterator type for result. However its const_iterator type can be
|
||||
* used to inspect its rows without changing them.
|
||||
*/
|
||||
class PQXX_LIBEXPORT const_result_iterator : public row
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
using value_type = row const;
|
||||
using pointer = row const *;
|
||||
using reference = row;
|
||||
using size_type = result_size_type;
|
||||
using difference_type = result_difference_type;
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
/// Create an iterator, but in an unusable state.
|
||||
const_result_iterator() noexcept = default;
|
||||
/// Copy an iterator.
|
||||
const_result_iterator(const_result_iterator const &) noexcept = default;
|
||||
/// Move an iterator.
|
||||
const_result_iterator(const_result_iterator &&) noexcept = default;
|
||||
|
||||
/// Begin iterating a @ref row.
|
||||
const_result_iterator(row const &t) noexcept : row{t} {}
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
|
||||
/**
|
||||
* @name Dereferencing operators
|
||||
*
|
||||
* An iterator "points to" its own row, which is also itself. This makes it
|
||||
* easy to address a @ref result as a two-dimensional container, without
|
||||
* going through the intermediate step of dereferencing the iterator. It
|
||||
* makes the interface similar to C pointer/array semantics.
|
||||
*
|
||||
* IIRC Alex Stepanov, the inventor of the STL, once remarked that having
|
||||
* this as standard behaviour for pointers would be useful in some
|
||||
* algorithms. So even if this makes me look foolish, I would seem to be in
|
||||
* distinguished company.
|
||||
*/
|
||||
//@{
|
||||
/// Dereference the iterator.
|
||||
[[nodiscard]] pointer operator->() const { return this; }
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
/// Dereference the iterator.
|
||||
[[nodiscard]] reference operator*() const { return *this; }
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Field access
|
||||
*/
|
||||
//@{
|
||||
using row::back;
|
||||
using row::front;
|
||||
using row::operator[];
|
||||
using row::at;
|
||||
using row::rownumber;
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Manipulations
|
||||
*/
|
||||
//@{
|
||||
const_result_iterator &operator=(const_result_iterator const &rhs)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
row::operator=(rhs);
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_result_iterator &operator=(const_result_iterator &&rhs)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
row::operator=(std::move(rhs));
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_result_iterator operator++(int);
|
||||
const_result_iterator &operator++()
|
||||
{
|
||||
++m_index;
|
||||
return *this;
|
||||
}
|
||||
const_result_iterator operator--(int);
|
||||
const_result_iterator &operator--()
|
||||
{
|
||||
--m_index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_result_iterator &operator+=(difference_type i)
|
||||
{
|
||||
m_index += i;
|
||||
return *this;
|
||||
}
|
||||
const_result_iterator &operator-=(difference_type i)
|
||||
{
|
||||
m_index -= i;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Interchange two iterators in an exception-safe manner.
|
||||
void swap(const_result_iterator &other) noexcept
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
row::swap(other);
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Comparisons
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] bool operator==(const_result_iterator const &i) const
|
||||
{
|
||||
return m_index == i.m_index;
|
||||
}
|
||||
[[nodiscard]] bool operator!=(const_result_iterator const &i) const
|
||||
{
|
||||
return m_index != i.m_index;
|
||||
}
|
||||
[[nodiscard]] bool operator<(const_result_iterator const &i) const
|
||||
{
|
||||
return m_index < i.m_index;
|
||||
}
|
||||
[[nodiscard]] bool operator<=(const_result_iterator const &i) const
|
||||
{
|
||||
return m_index <= i.m_index;
|
||||
}
|
||||
[[nodiscard]] bool operator>(const_result_iterator const &i) const
|
||||
{
|
||||
return m_index > i.m_index;
|
||||
}
|
||||
[[nodiscard]] bool operator>=(const_result_iterator const &i) const
|
||||
{
|
||||
return m_index >= i.m_index;
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Arithmetic operators
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] inline const_result_iterator operator+(difference_type) const;
|
||||
friend const_result_iterator
|
||||
operator+(difference_type, const_result_iterator const &);
|
||||
[[nodiscard]] inline const_result_iterator operator-(difference_type) const;
|
||||
[[nodiscard]] inline difference_type
|
||||
operator-(const_result_iterator const &) const;
|
||||
//@}
|
||||
|
||||
private:
|
||||
friend class pqxx::result;
|
||||
const_result_iterator(pqxx::result const *r, result_size_type i) noexcept :
|
||||
row{*r, i, r->columns()}
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
/// Reverse iterator for result. Use as result::const_reverse_iterator.
|
||||
class PQXX_LIBEXPORT const_reverse_result_iterator
|
||||
: private const_result_iterator
|
||||
{
|
||||
public:
|
||||
using super = const_result_iterator;
|
||||
using iterator_type = const_result_iterator;
|
||||
using iterator_type::difference_type;
|
||||
using iterator_type::iterator_category;
|
||||
using iterator_type::pointer;
|
||||
using value_type = iterator_type::value_type;
|
||||
using reference = iterator_type::reference;
|
||||
|
||||
/// Create an iterator, but in an unusable state.
|
||||
const_reverse_result_iterator() = default;
|
||||
/// Copy an iterator.
|
||||
const_reverse_result_iterator(const_reverse_result_iterator const &rhs) =
|
||||
default;
|
||||
/// Copy a reverse iterator from a regular iterator.
|
||||
explicit const_reverse_result_iterator(const_result_iterator const &rhs) :
|
||||
const_result_iterator{rhs}
|
||||
{
|
||||
super::operator--();
|
||||
}
|
||||
|
||||
/// Move a regular iterator into a reverse iterator.
|
||||
explicit const_reverse_result_iterator(const_result_iterator const &&rhs) :
|
||||
const_result_iterator{std::move(rhs)}
|
||||
{
|
||||
super::operator--();
|
||||
}
|
||||
|
||||
/// Return the underlying "regular" iterator (as per standard library).
|
||||
[[nodiscard]] PQXX_PURE const_result_iterator base() const noexcept;
|
||||
|
||||
/**
|
||||
* @name Dereferencing operators
|
||||
*/
|
||||
//@{
|
||||
/// Dereference iterator.
|
||||
using const_result_iterator::operator->;
|
||||
/// Dereference iterator.
|
||||
using const_result_iterator::operator*;
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Field access
|
||||
*/
|
||||
//@{
|
||||
using const_result_iterator::back;
|
||||
using const_result_iterator::front;
|
||||
using const_result_iterator::operator[];
|
||||
using const_result_iterator::at;
|
||||
using const_result_iterator::rownumber;
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Manipulations
|
||||
*/
|
||||
//@{
|
||||
const_reverse_result_iterator &
|
||||
operator=(const_reverse_result_iterator const &r)
|
||||
{
|
||||
iterator_type::operator=(r);
|
||||
return *this;
|
||||
}
|
||||
const_reverse_result_iterator &operator=(const_reverse_result_iterator &&r)
|
||||
{
|
||||
iterator_type::operator=(std::move(r));
|
||||
return *this;
|
||||
}
|
||||
const_reverse_result_iterator &operator++()
|
||||
{
|
||||
iterator_type::operator--();
|
||||
return *this;
|
||||
}
|
||||
const_reverse_result_iterator operator++(int);
|
||||
const_reverse_result_iterator &operator--()
|
||||
{
|
||||
iterator_type::operator++();
|
||||
return *this;
|
||||
}
|
||||
const_reverse_result_iterator operator--(int);
|
||||
const_reverse_result_iterator &operator+=(difference_type i)
|
||||
{
|
||||
iterator_type::operator-=(i);
|
||||
return *this;
|
||||
}
|
||||
const_reverse_result_iterator &operator-=(difference_type i)
|
||||
{
|
||||
iterator_type::operator+=(i);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(const_reverse_result_iterator &other) noexcept
|
||||
{
|
||||
const_result_iterator::swap(other);
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Arithmetic operators
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] const_reverse_result_iterator
|
||||
operator+(difference_type i) const
|
||||
{
|
||||
return const_reverse_result_iterator(base() - i);
|
||||
}
|
||||
[[nodiscard]] const_reverse_result_iterator operator-(difference_type i)
|
||||
{
|
||||
return const_reverse_result_iterator(base() + i);
|
||||
}
|
||||
[[nodiscard]] difference_type
|
||||
operator-(const_reverse_result_iterator const &rhs) const
|
||||
{
|
||||
return rhs.const_result_iterator::operator-(*this);
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Comparisons
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] bool
|
||||
operator==(const_reverse_result_iterator const &rhs) const noexcept
|
||||
{
|
||||
return iterator_type::operator==(rhs);
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
operator!=(const_reverse_result_iterator const &rhs) const noexcept
|
||||
{
|
||||
return not operator==(rhs);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator<(const_reverse_result_iterator const &rhs) const
|
||||
{
|
||||
return iterator_type::operator>(rhs);
|
||||
}
|
||||
[[nodiscard]] bool operator<=(const_reverse_result_iterator const &rhs) const
|
||||
{
|
||||
return iterator_type::operator>=(rhs);
|
||||
}
|
||||
[[nodiscard]] bool operator>(const_reverse_result_iterator const &rhs) const
|
||||
{
|
||||
return iterator_type::operator<(rhs);
|
||||
}
|
||||
[[nodiscard]] bool operator>=(const_reverse_result_iterator const &rhs) const
|
||||
{
|
||||
return iterator_type::operator<=(rhs);
|
||||
}
|
||||
//@}
|
||||
};
|
||||
|
||||
|
||||
inline const_result_iterator
|
||||
const_result_iterator::operator+(result::difference_type o) const
|
||||
{
|
||||
return {&m_result, size_type(result::difference_type(m_index) + o)};
|
||||
}
|
||||
|
||||
inline const_result_iterator
|
||||
operator+(result::difference_type o, const_result_iterator const &i)
|
||||
{
|
||||
return i + o;
|
||||
}
|
||||
|
||||
inline const_result_iterator
|
||||
const_result_iterator::operator-(result::difference_type o) const
|
||||
{
|
||||
return {&m_result, result_size_type(result::difference_type(m_index) - o)};
|
||||
}
|
||||
|
||||
inline result::difference_type
|
||||
const_result_iterator::operator-(const const_result_iterator &i) const
|
||||
{
|
||||
return result::difference_type(num() - i.num());
|
||||
}
|
||||
|
||||
inline const_result_iterator result::end() const noexcept
|
||||
{
|
||||
return {this, size()};
|
||||
}
|
||||
|
||||
|
||||
inline const_result_iterator result::cend() const noexcept
|
||||
{
|
||||
return end();
|
||||
}
|
||||
|
||||
|
||||
inline const_reverse_result_iterator
|
||||
operator+(result::difference_type n, const_reverse_result_iterator const &i)
|
||||
{
|
||||
return const_reverse_result_iterator{i.base() - n};
|
||||
}
|
||||
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,118 @@
|
||||
/** Internal wrapper for SQL cursors. Supports higher-level cursor classes.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY. Other headers include it for you.
|
||||
*
|
||||
* 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_SQL_CURSOR
|
||||
#define PQXX_H_SQL_CURSOR
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
/// Cursor with SQL positioning semantics.
|
||||
/** Thin wrapper around an SQL cursor, with SQL's ideas of positioning.
|
||||
*
|
||||
* SQL cursors have pre-increment/pre-decrement semantics, with on either end
|
||||
* of the result set a special position that does not repesent a row. This
|
||||
* class models SQL cursors for the purpose of implementing more C++-like
|
||||
* semantics on top.
|
||||
*
|
||||
* Positions of actual rows are numbered starting at 1. Position 0 exists but
|
||||
* does not refer to a row. There is a similar non-row position at the end of
|
||||
* the result set.
|
||||
*
|
||||
* Don't use this at home. You deserve better. Use the stateles_cursor
|
||||
* instead.
|
||||
*/
|
||||
class PQXX_LIBEXPORT sql_cursor : public cursor_base
|
||||
{
|
||||
public:
|
||||
sql_cursor(
|
||||
transaction_base &t, std::string_view query, std::string_view cname,
|
||||
cursor_base::access_policy ap, cursor_base::update_policy up,
|
||||
cursor_base::ownership_policy op, bool hold);
|
||||
|
||||
sql_cursor(
|
||||
transaction_base &t, std::string_view cname,
|
||||
cursor_base::ownership_policy op);
|
||||
|
||||
~sql_cursor() noexcept { close(); }
|
||||
|
||||
result fetch(difference_type rows, difference_type &displacement);
|
||||
result fetch(difference_type rows)
|
||||
{
|
||||
difference_type d = 0;
|
||||
return fetch(rows, d);
|
||||
}
|
||||
difference_type move(difference_type rows, difference_type &displacement);
|
||||
difference_type move(difference_type rows)
|
||||
{
|
||||
difference_type d = 0;
|
||||
return move(rows, d);
|
||||
}
|
||||
|
||||
/// Current position, or -1 for unknown
|
||||
/**
|
||||
* The starting position, just before the first row, counts as position zero.
|
||||
*
|
||||
* Position may be unknown if (and only if) this cursor was adopted, and has
|
||||
* never hit its starting position (position zero).
|
||||
*/
|
||||
difference_type pos() const noexcept { return m_pos; }
|
||||
|
||||
/// End position, or -1 for unknown
|
||||
/**
|
||||
* Returns the final position, just after the last row in the result set. The
|
||||
* starting position, just before the first row, counts as position zero.
|
||||
*
|
||||
* End position is unknown until it is encountered during use.
|
||||
*/
|
||||
difference_type endpos() const noexcept { return m_endpos; }
|
||||
|
||||
/// Return zero-row result for this cursor.
|
||||
result const &empty_result() const noexcept { return m_empty_result; }
|
||||
|
||||
void close() noexcept;
|
||||
|
||||
private:
|
||||
difference_type adjust(difference_type hoped, difference_type actual);
|
||||
static std::string stridestring(difference_type);
|
||||
/// Initialize cached empty result. Call only at beginning or end!
|
||||
void init_empty_result(transaction_base &);
|
||||
|
||||
/// Connection in which this cursor lives.
|
||||
connection &m_home;
|
||||
|
||||
/// Zero-row result from this cursor (or plain empty one if cursor is
|
||||
/// adopted)
|
||||
result m_empty_result;
|
||||
|
||||
result m_cached_current_row;
|
||||
|
||||
/// Is this cursor adopted (as opposed to created by this cursor object)?
|
||||
bool m_adopted;
|
||||
|
||||
/// Will this cursor object destroy its SQL cursor when it dies?
|
||||
cursor_base::ownership_policy m_ownership;
|
||||
|
||||
/// At starting position (-1), somewhere in the middle (0), or past end (1)
|
||||
int m_at_end;
|
||||
|
||||
/// Position, or -1 for unknown
|
||||
difference_type m_pos;
|
||||
|
||||
/// End position, or -1 for unknown
|
||||
difference_type m_endpos = -1;
|
||||
};
|
||||
|
||||
|
||||
PQXX_LIBEXPORT result_size_type obtain_stateless_cursor_size(sql_cursor &);
|
||||
PQXX_LIBEXPORT result stateless_cursor_retrieve(
|
||||
sql_cursor &, result::difference_type size,
|
||||
result::difference_type begin_pos, result::difference_type end_pos);
|
||||
} // namespace pqxx::internal
|
||||
#endif
|
||||
@@ -0,0 +1,131 @@
|
||||
/** Common implementation for statement parameter lists.
|
||||
*
|
||||
* These are used for both prepared statements and parameterized statements.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY. Other headers include it for you.
|
||||
*
|
||||
* 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_STATEMENT_PARAMETER
|
||||
#define PQXX_H_STATEMENT_PARAMETER
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "pqxx/binarystring.hxx"
|
||||
#include "pqxx/strconv.hxx"
|
||||
#include "pqxx/util.hxx"
|
||||
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
template<typename ITERATOR>
|
||||
constexpr inline auto const iterator_identity{
|
||||
[](decltype(*std::declval<ITERATOR>()) x) { return x; }};
|
||||
|
||||
|
||||
/// Marker type: pass a dynamically-determined number of statement parameters.
|
||||
/** @deprecated Use @ref params instead.
|
||||
*
|
||||
* Normally when invoking a prepared or parameterised statement, the number
|
||||
* of parameters is known at compile time. For instance,
|
||||
* `t.exec_prepared("foo", 1, "x");` executes statement `foo` with two
|
||||
* parameters, an `int` and a C string.
|
||||
*
|
||||
* But sometimes you may want to pass a number of parameters known only at run
|
||||
* time. In those cases, a @ref dynamic_params encodes a dynamically
|
||||
* determined number of parameters. You can mix these with regular, static
|
||||
* parameter lists, and you can re-use them for multiple statement invocations.
|
||||
*
|
||||
* A dynamic_params object does not store copies of its parameters, so make
|
||||
* sure they remain accessible until you've executed the statement.
|
||||
*
|
||||
* The ACCESSOR is an optional callable (such as a lambda). If you pass an
|
||||
* accessor `a`, then each parameter `p` goes into your statement as `a(p)`.
|
||||
*/
|
||||
template<typename IT, typename ACCESSOR = decltype(iterator_identity<IT>)>
|
||||
class dynamic_params
|
||||
{
|
||||
public:
|
||||
/// Wrap a sequence of pointers or iterators.
|
||||
constexpr dynamic_params(IT begin, IT end) :
|
||||
m_begin(begin), m_end(end), m_accessor(iterator_identity<IT>)
|
||||
{}
|
||||
|
||||
/// Wrap a sequence of pointers or iterators.
|
||||
/** This version takes an accessor callable. If you pass an accessor `acc`,
|
||||
* then any parameter `p` will go into the statement's parameter list as
|
||||
* `acc(p)`.
|
||||
*/
|
||||
constexpr dynamic_params(IT begin, IT end, ACCESSOR &acc) :
|
||||
m_begin(begin), m_end(end), m_accessor(acc)
|
||||
{}
|
||||
|
||||
/// Wrap a container.
|
||||
template<typename C>
|
||||
explicit constexpr dynamic_params(C &container) :
|
||||
dynamic_params(std::begin(container), std::end(container))
|
||||
{}
|
||||
|
||||
/// Wrap a container.
|
||||
/** This version takes an accessor callable. If you pass an accessor `acc`,
|
||||
* then any parameter `p` will go into the statement's parameter list as
|
||||
* `acc(p)`.
|
||||
*/
|
||||
template<typename C>
|
||||
explicit constexpr dynamic_params(C &container, ACCESSOR &acc) :
|
||||
dynamic_params(std::begin(container), std::end(container), acc)
|
||||
{}
|
||||
|
||||
constexpr IT begin() const noexcept { return m_begin; }
|
||||
constexpr IT end() const noexcept { return m_end; }
|
||||
|
||||
constexpr auto access(decltype(*std::declval<IT>()) value) const
|
||||
-> decltype(std::declval<ACCESSOR>()(value))
|
||||
{
|
||||
return m_accessor(value);
|
||||
}
|
||||
|
||||
private:
|
||||
IT const m_begin, m_end;
|
||||
ACCESSOR m_accessor = iterator_identity<IT>;
|
||||
};
|
||||
|
||||
|
||||
/// Internal type: encode statement parameters.
|
||||
/** Compiles arguments for prepared statements and parameterised queries into
|
||||
* a format that can be passed into libpq.
|
||||
*
|
||||
* Objects of this type are meant to be short-lived: a `c_params` lives and
|
||||
* dies entirely within the call to execute. So, for example, if you pass in a
|
||||
* non-null pointer as a parameter, @ref params may simply use that pointer as
|
||||
* a parameter value, without arranging longer-term storage for the data to
|
||||
* which it points. All values referenced by parameters must remain "live"
|
||||
* until the parameterised or prepared statement has been executed.
|
||||
*/
|
||||
struct PQXX_LIBEXPORT c_params
|
||||
{
|
||||
c_params() = default;
|
||||
/// Copying these objects is pointless and expensive. Don't do it.
|
||||
c_params(c_params const &) = delete;
|
||||
c_params(c_params &&) = default;
|
||||
|
||||
/// Pre-allocate storage for `n` parameters.
|
||||
void reserve(std::size_t n) &;
|
||||
|
||||
/// As used by libpq: pointers to parameter values.
|
||||
std::vector<char const *> values;
|
||||
/// As used by libpq: lengths of non-null arguments, in bytes.
|
||||
std::vector<int> lengths;
|
||||
/// As used by libpq: effectively boolean "is this a binary parameter?"
|
||||
std::vector<format> formats;
|
||||
};
|
||||
} // namespace pqxx::internal
|
||||
#endif
|
||||
@@ -0,0 +1,105 @@
|
||||
/** Stream iterators.
|
||||
*
|
||||
* 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_STREAM_ITERATOR
|
||||
#define PQXX_H_STREAM_ITERATOR
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
class stream_from;
|
||||
}
|
||||
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
// C++20: Replace with generator?
|
||||
/// Input iterator for stream_from.
|
||||
/** Just barely enough to support range-based "for" loops. Don't assume that
|
||||
* any of the usual behaviour works beyond that.
|
||||
*/
|
||||
template<typename... TYPE> class stream_input_iterator
|
||||
{
|
||||
public:
|
||||
using value_type = std::tuple<TYPE...>;
|
||||
|
||||
/// Construct an "end" iterator.
|
||||
stream_input_iterator() = default;
|
||||
|
||||
explicit stream_input_iterator(stream_from &home) : m_home(&home)
|
||||
{
|
||||
advance();
|
||||
}
|
||||
stream_input_iterator(stream_input_iterator const &) = default;
|
||||
|
||||
stream_input_iterator &operator++()
|
||||
{
|
||||
advance();
|
||||
return *this;
|
||||
}
|
||||
|
||||
value_type const &operator*() const { return m_value; }
|
||||
|
||||
/// Comparison only works for comparing to end().
|
||||
bool operator==(stream_input_iterator const &rhs) const
|
||||
{
|
||||
return m_home == rhs.m_home;
|
||||
}
|
||||
/// Comparison only works for comparing to end().
|
||||
bool operator!=(stream_input_iterator const &rhs) const
|
||||
{
|
||||
return not(*this == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
void advance()
|
||||
{
|
||||
if (m_home == nullptr)
|
||||
throw usage_error{"Moving stream_from iterator beyond end()."};
|
||||
if (not((*m_home) >> m_value))
|
||||
m_home = nullptr;
|
||||
}
|
||||
|
||||
stream_from *m_home{nullptr};
|
||||
value_type m_value;
|
||||
};
|
||||
|
||||
|
||||
// C++20: Replace with generator?
|
||||
/// Iteration over a @ref stream_from.
|
||||
template<typename... TYPE> class stream_input_iteration
|
||||
{
|
||||
public:
|
||||
using iterator = stream_input_iterator<TYPE...>;
|
||||
explicit stream_input_iteration(stream_from &home) : m_home{home} {}
|
||||
iterator begin() const { return iterator{m_home}; }
|
||||
iterator end() const { return {}; }
|
||||
|
||||
private:
|
||||
stream_from &m_home;
|
||||
};
|
||||
|
||||
|
||||
// C++20: Replace with generator?
|
||||
/// Iteration over a @ref stream_from, deleting it once done.
|
||||
template<typename... TYPE> class owning_stream_input_iteration
|
||||
{
|
||||
public:
|
||||
using iterator = stream_input_iterator<TYPE...>;
|
||||
explicit owning_stream_input_iteration(std::unique_ptr<stream_from> &&home) :
|
||||
m_home{std::move(home)}
|
||||
{}
|
||||
iterator begin() const { return iterator{*m_home.get()}; }
|
||||
iterator end() const { return {}; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<stream_from> m_home;
|
||||
};
|
||||
} // namespace pqxx::internal
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
#if !defined(PQXX_WAIT_HXX)
|
||||
# define PQXX_WAIT_HXX
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
/// Wait.
|
||||
/** This is normally `std::this_thread::sleep_for()`. But MinGW's `thread`
|
||||
* header doesn't work, so we must be careful about including it.
|
||||
*/
|
||||
void PQXX_LIBEXPORT wait_for(unsigned int microseconds);
|
||||
|
||||
|
||||
/// Wait for a socket to be ready for reading/writing, or timeout.
|
||||
PQXX_LIBEXPORT void wait_fd(
|
||||
int fd, bool for_read, bool for_write, unsigned seconds = 1,
|
||||
unsigned microseconds = 0);
|
||||
} // namespace pqxx::internal
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** Transaction isolation levels.
|
||||
*
|
||||
* Policies and traits describing SQL transaction isolation levels
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/isolation.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,75 @@
|
||||
/* Definitions for transaction isolation levels, and such.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/isolation 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_ISOLATION
|
||||
#define PQXX_H_ISOLATION
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/util.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Should a transaction be read-only, or read-write?
|
||||
/** No, this is not an isolation level. So it really doesn't belong here.
|
||||
* But it's not really worth a separate header.
|
||||
*/
|
||||
enum class write_policy
|
||||
{
|
||||
read_only,
|
||||
read_write
|
||||
};
|
||||
|
||||
|
||||
/// Transaction isolation levels.
|
||||
/** These are as defined in the SQL standard. But there are a few notes
|
||||
* specific to PostgreSQL.
|
||||
*
|
||||
* First, postgres does not support "read uncommitted." The lowest level you
|
||||
* can get is "read committed," which is better. PostgreSQL is built on the
|
||||
* MVCC paradigm, which guarantees "read committed" isolation without any
|
||||
* additional performance overhead, so there was no point in providing the
|
||||
* lower level.
|
||||
*
|
||||
* Second, "repeatable read" also makes more isolation guarantees than the
|
||||
* standard requires. According to the standard, this level prevents "dirty
|
||||
* reads" and "nonrepeatable reads," but not "phantom reads." In postgres,
|
||||
* it actually prevents all three.
|
||||
*
|
||||
* Third, "serializable" is only properly supported starting at postgres 9.1.
|
||||
* If you request "serializable" isolation on an older backend, you will get
|
||||
* the same isolation as in "repeatable read." It's better than the
|
||||
* "repeatable read" defined in the SQL standard, but not a complete
|
||||
* implementation of the standard's "serializable" isolation level.
|
||||
*
|
||||
* In general, a lower isolation level will allow more surprising interactions
|
||||
* between ongoing transactions, but improve performance. A higher level
|
||||
* gives you more protection from subtle concurrency bugs, but sometimes it
|
||||
* may not be possible to complete your transaction without avoiding paradoxes
|
||||
* in the data. In that case a transaction may fail, and the application will
|
||||
* have to re-do the whole thing based on the latest state of the database.
|
||||
* (If you want to retry your code in that situation, have a look at the
|
||||
* transactor framework.)
|
||||
*
|
||||
* Study the levels and design your application with the right level in mind.
|
||||
*/
|
||||
enum isolation_level
|
||||
{
|
||||
// PostgreSQL only has the better isolation levels.
|
||||
// read_uncommitted,
|
||||
|
||||
read_committed,
|
||||
repeatable_read,
|
||||
serializable,
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** Large Objects interface.
|
||||
*
|
||||
* Supports direct access to large objects, as well as through I/O streams
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/largeobject.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,735 @@
|
||||
/* Large Objects interface. Deprecated; use blob instead.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/largeobject 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_LARGEOBJECT
|
||||
#define PQXX_H_LARGEOBJECT
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <streambuf>
|
||||
|
||||
#include "pqxx/dbtransaction.hxx"
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Identity of a large object.
|
||||
/** @deprecated Use the @ref blob class instead.
|
||||
*
|
||||
* Encapsulates the identity of a large object.
|
||||
*
|
||||
* A largeobject must be accessed only from within a backend transaction, but
|
||||
* the object's identity remains valid as long as the object exists.
|
||||
*/
|
||||
class PQXX_LIBEXPORT largeobject
|
||||
{
|
||||
public:
|
||||
using size_type = large_object_size_type;
|
||||
|
||||
/// Refer to a nonexistent large object (similar to what a null pointer
|
||||
/// does).
|
||||
[[deprecated("Use blob instead.")]] largeobject() noexcept = default;
|
||||
|
||||
/// Create new large object.
|
||||
/** @param t Backend transaction in which the object is to be created.
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] explicit largeobject(dbtransaction &t);
|
||||
|
||||
/// Wrap object with given oid.
|
||||
/** Convert combination of a transaction and object identifier into a
|
||||
* large object identity. Does not affect the database.
|
||||
* @param o Object identifier for the given object.
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] explicit largeobject(oid o) noexcept :
|
||||
m_id{o}
|
||||
{}
|
||||
|
||||
/// Import large object from a local file.
|
||||
/** Creates a large object containing the data found in the given file.
|
||||
* @param t Backend transaction in which the large object is to be created.
|
||||
* @param file A filename on the client program's filesystem.
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] largeobject(
|
||||
dbtransaction &t, std::string_view file);
|
||||
|
||||
/// Take identity of an opened large object.
|
||||
/** Copy identity of already opened large object. Note that this may be done
|
||||
* as an implicit conversion.
|
||||
* @param o Already opened large object to copy identity from.
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] largeobject(
|
||||
largeobjectaccess const &o) noexcept;
|
||||
|
||||
/// Object identifier.
|
||||
/** The number returned by this function identifies the large object in the
|
||||
* database we're connected to (or oid_none is returned if we refer to the
|
||||
* null object).
|
||||
*/
|
||||
[[nodiscard]] oid id() const noexcept { return m_id; }
|
||||
|
||||
/**
|
||||
* @name Identity comparisons
|
||||
*
|
||||
* These operators compare the object identifiers of large objects. This has
|
||||
* nothing to do with the objects' actual contents; use them only for keeping
|
||||
* track of containers of references to large objects and such.
|
||||
*/
|
||||
//@{
|
||||
/// Compare object identities
|
||||
/** @warning Only valid between large objects in the same database. */
|
||||
[[nodiscard]] bool operator==(largeobject const &other) const
|
||||
{
|
||||
return m_id == other.m_id;
|
||||
}
|
||||
/// Compare object identities
|
||||
/** @warning Only valid between large objects in the same database. */
|
||||
[[nodiscard]] bool operator!=(largeobject const &other) const
|
||||
{
|
||||
return m_id != other.m_id;
|
||||
}
|
||||
/// Compare object identities
|
||||
/** @warning Only valid between large objects in the same database. */
|
||||
[[nodiscard]] bool operator<=(largeobject const &other) const
|
||||
{
|
||||
return m_id <= other.m_id;
|
||||
}
|
||||
/// Compare object identities
|
||||
/** @warning Only valid between large objects in the same database. */
|
||||
[[nodiscard]] bool operator>=(largeobject const &other) const
|
||||
{
|
||||
return m_id >= other.m_id;
|
||||
}
|
||||
/// Compare object identities
|
||||
/** @warning Only valid between large objects in the same database. */
|
||||
[[nodiscard]] bool operator<(largeobject const &other) const
|
||||
{
|
||||
return m_id < other.m_id;
|
||||
}
|
||||
/// Compare object identities
|
||||
/** @warning Only valid between large objects in the same database. */
|
||||
[[nodiscard]] bool operator>(largeobject const &other) const
|
||||
{
|
||||
return m_id > other.m_id;
|
||||
}
|
||||
//@}
|
||||
|
||||
/// Export large object's contents to a local file
|
||||
/** Writes the data stored in the large object to the given file.
|
||||
* @param t Transaction in which the object is to be accessed
|
||||
* @param file A filename on the client's filesystem
|
||||
*/
|
||||
void to_file(dbtransaction &t, std::string_view file) const;
|
||||
|
||||
/// Delete large object from database
|
||||
/** Unlike its low-level equivalent cunlink, this will throw an exception if
|
||||
* deletion fails.
|
||||
* @param t Transaction in which the object is to be deleted
|
||||
*/
|
||||
void remove(dbtransaction &t) const;
|
||||
|
||||
protected:
|
||||
PQXX_PURE static internal::pq::PGconn *
|
||||
raw_connection(dbtransaction const &T);
|
||||
|
||||
PQXX_PRIVATE std::string reason(connection const &, int err) const;
|
||||
|
||||
private:
|
||||
oid m_id = oid_none;
|
||||
};
|
||||
|
||||
|
||||
/// Accessor for large object's contents.
|
||||
/** @deprecated Use the `blob` class instead.
|
||||
*/
|
||||
class PQXX_LIBEXPORT largeobjectaccess : private largeobject
|
||||
{
|
||||
public:
|
||||
using largeobject::size_type;
|
||||
using off_type = size_type;
|
||||
using pos_type = size_type;
|
||||
|
||||
/// Open mode: `in`, `out` (can be combined using "bitwise or").
|
||||
/** According to the C++ standard, these should be in `std::ios_base`. We
|
||||
* take them from derived class `std::ios` instead, which is easier on the
|
||||
* eyes.
|
||||
*
|
||||
* Historical note: taking it from std::ios was originally a workaround for a
|
||||
* problem with gcc 2.95.
|
||||
*/
|
||||
using openmode = std::ios::openmode;
|
||||
|
||||
/// Default open mode: in, out, binary.
|
||||
static constexpr auto default_mode{
|
||||
std::ios::in | std::ios::out | std::ios::binary};
|
||||
|
||||
/// Seek direction: `beg`, `cur`, `end`.
|
||||
using seekdir = std::ios::seekdir;
|
||||
|
||||
/// Create new large object and open it.
|
||||
/**
|
||||
* @param t Backend transaction in which the object is to be created.
|
||||
* @param mode Access mode, defaults to ios_base::in | ios_base::out |
|
||||
* ios_base::binary.
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] explicit largeobjectaccess(
|
||||
dbtransaction &t, openmode mode = default_mode);
|
||||
|
||||
/// Open large object with given oid.
|
||||
/** Convert combination of a transaction and object identifier into a
|
||||
* large object identity. Does not affect the database.
|
||||
* @param t Transaction in which the object is to be accessed.
|
||||
* @param o Object identifier for the given object.
|
||||
* @param mode Access mode, defaults to ios_base::in | ios_base::out |
|
||||
* ios_base::binary.
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] largeobjectaccess(
|
||||
dbtransaction &t, oid o, openmode mode = default_mode);
|
||||
|
||||
/// Open given large object.
|
||||
/** Open a large object with the given identity for reading and/or writing.
|
||||
* @param t Transaction in which the object is to be accessed.
|
||||
* @param o Identity for the large object to be accessed.
|
||||
* @param mode Access mode, defaults to ios_base::in | ios_base::out |
|
||||
* ios_base::binary.
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] largeobjectaccess(
|
||||
dbtransaction &t, largeobject o, openmode mode = default_mode);
|
||||
|
||||
/// Import large object from a local file and open it.
|
||||
/** Creates a large object containing the data found in the given file.
|
||||
* @param t Backend transaction in which the large object is to be created.
|
||||
* @param file A filename on the client program's filesystem.
|
||||
* @param mode Access mode, defaults to ios_base::in | ios_base::out.
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] largeobjectaccess(
|
||||
dbtransaction &t, std::string_view file, openmode mode = default_mode);
|
||||
|
||||
~largeobjectaccess() noexcept { close(); }
|
||||
|
||||
/// Object identifier.
|
||||
/** The number returned by this function uniquely identifies the large object
|
||||
* in the context of the database we're connected to.
|
||||
*/
|
||||
using largeobject::id;
|
||||
|
||||
/// Export large object's contents to a local file.
|
||||
/** Writes the data stored in the large object to the given file.
|
||||
* @param file A filename on the client's filesystem.
|
||||
*/
|
||||
void to_file(std::string_view file) const
|
||||
{
|
||||
largeobject::to_file(m_trans, file);
|
||||
}
|
||||
|
||||
using largeobject::to_file;
|
||||
|
||||
/**
|
||||
* @name High-level access to object contents.
|
||||
*/
|
||||
//@{
|
||||
/// Write data to large object.
|
||||
/** @warning The size of a write is currently limited to 2GB.
|
||||
*
|
||||
* @param buf Data to write.
|
||||
* @param len Number of bytes from Buf to write.
|
||||
*/
|
||||
void write(char const buf[], std::size_t len);
|
||||
|
||||
/// Write string to large object.
|
||||
/** If not all bytes could be written, an exception is thrown.
|
||||
* @param buf Data to write; no terminating zero is written.
|
||||
*/
|
||||
void write(std::string_view buf) { write(std::data(buf), std::size(buf)); }
|
||||
|
||||
/// Read data from large object.
|
||||
/** Throws an exception if an error occurs while reading.
|
||||
* @param buf Location to store the read data in.
|
||||
* @param len Number of bytes to try and read.
|
||||
* @return Number of bytes read, which may be less than the number requested
|
||||
* if the end of the large object is reached.
|
||||
*/
|
||||
size_type read(char buf[], std::size_t len);
|
||||
|
||||
/// Seek in large object's data stream.
|
||||
/** Throws an exception if an error occurs.
|
||||
* @return The new position in the large object
|
||||
*/
|
||||
size_type seek(size_type dest, seekdir dir);
|
||||
|
||||
/// Report current position in large object's data stream.
|
||||
/** Throws an exception if an error occurs.
|
||||
* @return The current position in the large object.
|
||||
*/
|
||||
[[nodiscard]] size_type tell() const;
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Low-level access to object contents.
|
||||
*
|
||||
* These functions provide a more "C-like" access interface, returning
|
||||
* special values instead of throwing exceptions on error. These functions
|
||||
* are generally best avoided in favour of the high-level access functions,
|
||||
* which behave more like C++ functions should.
|
||||
*
|
||||
* Due to libpq's underlying API, some operations are limited to "int"
|
||||
* sizes, typically 2 GB, even though a large object can grow much larger.
|
||||
*/
|
||||
//@{
|
||||
/// Seek in large object's data stream.
|
||||
/** Does not throw exception in case of error; inspect return value and
|
||||
* `errno` instead.
|
||||
* @param dest Offset to go to.
|
||||
* @param dir Origin to which dest is relative: ios_base::beg (from beginning
|
||||
* of the object), ios_base::cur (from current access position), or
|
||||
* ios_base;:end (from end of object).
|
||||
* @return New position in large object, or -1 if an error occurred.
|
||||
*/
|
||||
pos_type cseek(off_type dest, seekdir dir) noexcept;
|
||||
|
||||
/// Write to large object's data stream.
|
||||
/** Does not throw exception in case of error; inspect return value and
|
||||
* `errno` instead.
|
||||
* @param buf Data to write.
|
||||
* @param len Number of bytes to write.
|
||||
* @return Number of bytes actually written, or -1 if an error occurred.
|
||||
*/
|
||||
off_type cwrite(char const buf[], std::size_t len) noexcept;
|
||||
|
||||
/// Read from large object's data stream.
|
||||
/** Does not throw exception in case of error; inspect return value and
|
||||
* `errno` instead.
|
||||
* @param buf Area where incoming bytes should be stored.
|
||||
* @param len Number of bytes to read.
|
||||
* @return Number of bytes actually read, or -1 if an error occurred..
|
||||
*/
|
||||
off_type cread(char buf[], std::size_t len) noexcept;
|
||||
|
||||
/// Report current position in large object's data stream.
|
||||
/** Does not throw exception in case of error; inspect return value and
|
||||
* `errno` instead.
|
||||
* @return Current position in large object, of -1 if an error occurred.
|
||||
*/
|
||||
[[nodiscard]] pos_type ctell() const noexcept;
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Error/warning output
|
||||
*/
|
||||
//@{
|
||||
/// Issue message to transaction's notice processor.
|
||||
void process_notice(zview) noexcept;
|
||||
//@}
|
||||
|
||||
using largeobject::remove;
|
||||
|
||||
using largeobject::operator==;
|
||||
using largeobject::operator!=;
|
||||
using largeobject::operator<;
|
||||
using largeobject::operator<=;
|
||||
using largeobject::operator>;
|
||||
using largeobject::operator>=;
|
||||
|
||||
largeobjectaccess() = delete;
|
||||
largeobjectaccess(largeobjectaccess const &) = delete;
|
||||
largeobjectaccess operator=(largeobjectaccess const &) = delete;
|
||||
|
||||
private:
|
||||
PQXX_PRIVATE std::string reason(int err) const;
|
||||
internal::pq::PGconn *raw_connection() const
|
||||
{
|
||||
return largeobject::raw_connection(m_trans);
|
||||
}
|
||||
|
||||
PQXX_PRIVATE void open(openmode mode);
|
||||
void close() noexcept;
|
||||
|
||||
dbtransaction &m_trans;
|
||||
int m_fd = -1;
|
||||
};
|
||||
|
||||
|
||||
/// Streambuf to use large objects in standard I/O streams.
|
||||
/** @deprecated Access large objects directly using the @ref blob class.
|
||||
*
|
||||
* The standard streambuf classes provide uniform access to data storage such
|
||||
* as files or string buffers, so they can be accessed using standard input or
|
||||
* output streams. This streambuf implementation provided similar access to
|
||||
* large objects, so they could be read and written using the same stream
|
||||
* classes.
|
||||
*
|
||||
* This functionality was considered too fragile and complex, so it has been
|
||||
* replaced with a single, much simpler class.
|
||||
*/
|
||||
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
|
||||
class largeobject_streambuf : public std::basic_streambuf<CHAR, TRAITS>
|
||||
{
|
||||
using size_type = largeobject::size_type;
|
||||
|
||||
public:
|
||||
using char_type = CHAR;
|
||||
using traits_type = TRAITS;
|
||||
using int_type = typename traits_type::int_type;
|
||||
using pos_type = typename traits_type::pos_type;
|
||||
using off_type = typename traits_type::off_type;
|
||||
using openmode = largeobjectaccess::openmode;
|
||||
using seekdir = largeobjectaccess::seekdir;
|
||||
|
||||
/// Default open mode: in, out, binary.
|
||||
static constexpr auto default_mode{
|
||||
std::ios::in | std::ios::out | std::ios::binary};
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
[[deprecated("Use blob instead.")]] largeobject_streambuf(
|
||||
dbtransaction &t, largeobject o, openmode mode = default_mode,
|
||||
size_type buf_size = 512) :
|
||||
m_bufsize{buf_size}, m_obj{t, o, mode}, m_g{nullptr}, m_p{nullptr}
|
||||
{
|
||||
initialize(mode);
|
||||
}
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
|
||||
[[deprecated("Use blob instead.")]] largeobject_streambuf(
|
||||
dbtransaction &t, oid o, openmode mode = default_mode,
|
||||
size_type buf_size = 512) :
|
||||
m_bufsize{buf_size}, m_obj{t, o, mode}, m_g{nullptr}, m_p{nullptr}
|
||||
{
|
||||
initialize(mode);
|
||||
}
|
||||
|
||||
virtual ~largeobject_streambuf() noexcept
|
||||
{
|
||||
delete[] m_p;
|
||||
delete[] m_g;
|
||||
}
|
||||
|
||||
/// For use by large object stream classes.
|
||||
void process_notice(zview const &s) { m_obj.process_notice(s); }
|
||||
|
||||
protected:
|
||||
virtual int sync() override
|
||||
{
|
||||
// setg() sets eback, gptr, egptr.
|
||||
this->setg(this->eback(), this->eback(), this->egptr());
|
||||
return overflow(eof());
|
||||
}
|
||||
|
||||
virtual pos_type seekoff(off_type offset, seekdir dir, openmode) override
|
||||
{
|
||||
return adjust_eof(m_obj.cseek(largeobjectaccess::off_type(offset), dir));
|
||||
}
|
||||
|
||||
virtual pos_type seekpos(pos_type pos, openmode) override
|
||||
{
|
||||
largeobjectaccess::pos_type const newpos{
|
||||
m_obj.cseek(largeobjectaccess::off_type(pos), std::ios::beg)};
|
||||
return adjust_eof(newpos);
|
||||
}
|
||||
|
||||
virtual int_type overflow(int_type ch) override
|
||||
{
|
||||
auto *const pp{this->pptr()};
|
||||
if (pp == nullptr)
|
||||
return eof();
|
||||
auto *const pb{this->pbase()};
|
||||
int_type res{0};
|
||||
|
||||
if (pp > pb)
|
||||
{
|
||||
auto const write_sz{pp - pb};
|
||||
auto const written_sz{
|
||||
m_obj.cwrite(pb, static_cast<std::size_t>(pp - pb))};
|
||||
if (internal::cmp_less_equal(written_sz, 0))
|
||||
throw internal_error{
|
||||
"pqxx::largeobject: write failed "
|
||||
"(is transaction still valid on write or flush?), "
|
||||
"libpq reports error"};
|
||||
else if (write_sz != written_sz)
|
||||
throw internal_error{
|
||||
"pqxx::largeobject: write failed "
|
||||
"(is transaction still valid on write or flush?), " +
|
||||
std::to_string(written_sz) + "/" + std::to_string(write_sz) +
|
||||
" bytes written"};
|
||||
auto const out{adjust_eof(written_sz)};
|
||||
|
||||
if constexpr (std::is_arithmetic_v<decltype(out)>)
|
||||
res = check_cast<int_type>(out, "largeobject position"sv);
|
||||
else
|
||||
res = int_type(out);
|
||||
}
|
||||
this->setp(m_p, m_p + m_bufsize);
|
||||
|
||||
// Write that one more character, if it's there.
|
||||
if (ch != eof())
|
||||
{
|
||||
*this->pptr() = static_cast<char_type>(ch);
|
||||
this->pbump(1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
virtual int_type overflow() { return overflow(eof()); }
|
||||
|
||||
virtual int_type underflow() override
|
||||
{
|
||||
if (this->gptr() == nullptr)
|
||||
return eof();
|
||||
auto *const eb{this->eback()};
|
||||
auto const res{adjust_eof(
|
||||
m_obj.cread(this->eback(), static_cast<std::size_t>(m_bufsize)))};
|
||||
this->setg(
|
||||
eb, eb, eb + (res == eof() ? 0 : static_cast<std::size_t>(res)));
|
||||
return (res == eof() or res == 0) ? eof() : traits_type::to_int_type(*eb);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Shortcut for traits_type::eof().
|
||||
static int_type eof() { return traits_type::eof(); }
|
||||
|
||||
/// Helper: change error position of -1 to EOF (probably a no-op).
|
||||
template<typename INTYPE> static std::streampos adjust_eof(INTYPE pos)
|
||||
{
|
||||
bool const at_eof{pos == -1};
|
||||
if constexpr (std::is_arithmetic_v<std::streampos>)
|
||||
{
|
||||
return check_cast<std::streampos>(
|
||||
(at_eof ? eof() : pos), "large object seek"sv);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::streampos(at_eof ? eof() : pos);
|
||||
}
|
||||
}
|
||||
|
||||
void initialize(openmode mode)
|
||||
{
|
||||
if ((mode & std::ios::in) != 0)
|
||||
{
|
||||
m_g = new char_type[unsigned(m_bufsize)];
|
||||
this->setg(m_g, m_g, m_g);
|
||||
}
|
||||
if ((mode & std::ios::out) != 0)
|
||||
{
|
||||
m_p = new char_type[unsigned(m_bufsize)];
|
||||
this->setp(m_p, m_p + m_bufsize);
|
||||
}
|
||||
}
|
||||
|
||||
size_type const m_bufsize;
|
||||
largeobjectaccess m_obj;
|
||||
|
||||
/// Get & put buffers.
|
||||
char_type *m_g, *m_p;
|
||||
};
|
||||
|
||||
|
||||
/// Input stream that gets its data from a large object.
|
||||
/** @deprecated Access large objects directly using the @ref blob class.
|
||||
*
|
||||
* This class worked like any other istream, but to read data from a large
|
||||
* object. It supported all formatting and streaming operations of
|
||||
* `std::istream`.
|
||||
*
|
||||
* This functionality was considered too fragile and complex, so it has been
|
||||
* replaced with a single, much simpler class.
|
||||
*/
|
||||
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
|
||||
class basic_ilostream : public std::basic_istream<CHAR, TRAITS>
|
||||
{
|
||||
using super = std::basic_istream<CHAR, TRAITS>;
|
||||
|
||||
public:
|
||||
using char_type = CHAR;
|
||||
using traits_type = TRAITS;
|
||||
using int_type = typename traits_type::int_type;
|
||||
using pos_type = typename traits_type::pos_type;
|
||||
using off_type = typename traits_type::off_type;
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
/// Create a basic_ilostream.
|
||||
/**
|
||||
* @param t Transaction in which this stream is to exist.
|
||||
* @param o Large object to access.
|
||||
* @param buf_size Size of buffer to use internally (optional).
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] basic_ilostream(
|
||||
dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) :
|
||||
super{nullptr},
|
||||
m_buf{t, o, std::ios::in | std::ios::binary, buf_size}
|
||||
{
|
||||
super::init(&m_buf);
|
||||
}
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
|
||||
/// Create a basic_ilostream.
|
||||
/**
|
||||
* @param t Transaction in which this stream is to exist.
|
||||
* @param o Identifier of a large object to access.
|
||||
* @param buf_size Size of buffer to use internally (optional).
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] basic_ilostream(
|
||||
dbtransaction &t, oid o, largeobject::size_type buf_size = 512) :
|
||||
super{nullptr},
|
||||
m_buf{t, o, std::ios::in | std::ios::binary, buf_size}
|
||||
{
|
||||
super::init(&m_buf);
|
||||
}
|
||||
|
||||
private:
|
||||
largeobject_streambuf<CHAR, TRAITS> m_buf;
|
||||
};
|
||||
|
||||
using ilostream = basic_ilostream<char>;
|
||||
|
||||
|
||||
/// Output stream that writes data back to a large object.
|
||||
/** @deprecated Access large objects directly using the @ref blob class.
|
||||
*
|
||||
* This worked like any other ostream, but to write data to a large object.
|
||||
* It supported all formatting and streaming operations of `std::ostream`.
|
||||
*
|
||||
* This functionality was considered too fragile and complex, so it has been
|
||||
* replaced with a single, much simpler class.
|
||||
*/
|
||||
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
|
||||
class basic_olostream : public std::basic_ostream<CHAR, TRAITS>
|
||||
{
|
||||
using super = std::basic_ostream<CHAR, TRAITS>;
|
||||
|
||||
public:
|
||||
using char_type = CHAR;
|
||||
using traits_type = TRAITS;
|
||||
using int_type = typename traits_type::int_type;
|
||||
using pos_type = typename traits_type::pos_type;
|
||||
using off_type = typename traits_type::off_type;
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
/// Create a basic_olostream.
|
||||
/**
|
||||
* @param t transaction in which this stream is to exist.
|
||||
* @param o a large object to access.
|
||||
* @param buf_size size of buffer to use internally (optional).
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] basic_olostream(
|
||||
dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) :
|
||||
super{nullptr},
|
||||
m_buf{t, o, std::ios::out | std::ios::binary, buf_size}
|
||||
{
|
||||
super::init(&m_buf);
|
||||
}
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
|
||||
/// Create a basic_olostream.
|
||||
/**
|
||||
* @param t transaction in which this stream is to exist.
|
||||
* @param o a large object to access.
|
||||
* @param buf_size size of buffer to use internally (optional).
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] basic_olostream(
|
||||
dbtransaction &t, oid o, largeobject::size_type buf_size = 512) :
|
||||
super{nullptr},
|
||||
m_buf{t, o, std::ios::out | std::ios::binary, buf_size}
|
||||
{
|
||||
super::init(&m_buf);
|
||||
}
|
||||
|
||||
~basic_olostream()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_buf.pubsync();
|
||||
m_buf.pubsync();
|
||||
}
|
||||
catch (std::exception const &e)
|
||||
{
|
||||
m_buf.process_notice(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
largeobject_streambuf<CHAR, TRAITS> m_buf;
|
||||
};
|
||||
|
||||
using olostream = basic_olostream<char>;
|
||||
|
||||
|
||||
/// Stream that reads and writes a large object.
|
||||
/** @deprecated Access large objects directly using the @ref blob class.
|
||||
*
|
||||
* This worked like a std::iostream, but to read data from, or write data to, a
|
||||
* large object. It supported all formatting and streaming operations of
|
||||
* `std::iostream`.
|
||||
*
|
||||
* This functionality was considered too fragile and complex, so it has been
|
||||
* replaced with a single, much simpler class.
|
||||
*/
|
||||
template<typename CHAR = char, typename TRAITS = std::char_traits<CHAR>>
|
||||
class basic_lostream : public std::basic_iostream<CHAR, TRAITS>
|
||||
{
|
||||
using super = std::basic_iostream<CHAR, TRAITS>;
|
||||
|
||||
public:
|
||||
using char_type = CHAR;
|
||||
using traits_type = TRAITS;
|
||||
using int_type = typename traits_type::int_type;
|
||||
using pos_type = typename traits_type::pos_type;
|
||||
using off_type = typename traits_type::off_type;
|
||||
|
||||
/// Create a basic_lostream.
|
||||
/**
|
||||
* @param t Transaction in which this stream is to exist.
|
||||
* @param o Large object to access.
|
||||
* @param buf_size Size of buffer to use internally (optional).
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] basic_lostream(
|
||||
dbtransaction &t, largeobject o, largeobject::size_type buf_size = 512) :
|
||||
super{nullptr},
|
||||
m_buf{
|
||||
t, o, std::ios::in | std::ios::out | std::ios::binary, buf_size}
|
||||
{
|
||||
super::init(&m_buf);
|
||||
}
|
||||
|
||||
/// Create a basic_lostream.
|
||||
/**
|
||||
* @param t Transaction in which this stream is to exist.
|
||||
* @param o Large object to access.
|
||||
* @param buf_size Size of buffer to use internally (optional).
|
||||
*/
|
||||
[[deprecated("Use blob instead.")]] basic_lostream(
|
||||
dbtransaction &t, oid o, largeobject::size_type buf_size = 512) :
|
||||
super{nullptr},
|
||||
m_buf{
|
||||
t, o, std::ios::in | std::ios::out | std::ios::binary, buf_size}
|
||||
{
|
||||
super::init(&m_buf);
|
||||
}
|
||||
|
||||
~basic_lostream()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_buf.pubsync();
|
||||
m_buf.pubsync();
|
||||
}
|
||||
catch (std::exception const &e)
|
||||
{
|
||||
m_buf.process_notice(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
largeobject_streambuf<CHAR, TRAITS> m_buf;
|
||||
};
|
||||
|
||||
using lostream = basic_lostream<char>;
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::nontransaction class.
|
||||
*
|
||||
* pqxx::nontransaction provides nontransactional database access.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/nontransaction.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,76 @@
|
||||
/* Definition of the pqxx::nontransaction class.
|
||||
*
|
||||
* pqxx::nontransaction provides nontransactional database access
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/nontransaction 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_NONTRANSACTION
|
||||
#define PQXX_H_NONTRANSACTION
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/connection.hxx"
|
||||
#include "pqxx/result.hxx"
|
||||
#include "pqxx/transaction.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
/// Simple "transaction" class offering no transactional integrity.
|
||||
/**
|
||||
* @ingroup transactions
|
||||
*
|
||||
* nontransaction, like transaction or any other transaction_base-derived
|
||||
* class, provides access to a database through a connection. Unlike its
|
||||
* siblings, however, nontransaction does not maintain any kind of
|
||||
* transactional integrity. This may be useful eg. for read-only access to the
|
||||
* database that does not require a consistent, atomic view on its data; or for
|
||||
* operations that are not allowed within a backend transaction, such as
|
||||
* creating tables.
|
||||
*
|
||||
* For queries that update the database, however, a real transaction is likely
|
||||
* to be faster unless the transaction consists of only a single record update.
|
||||
*
|
||||
* Also, you can keep a nontransaction open for as long as you like. Actual
|
||||
* back-end transactions are limited in lifespan, and will sometimes fail just
|
||||
* because they took too long to execute or were left idle for too long. This
|
||||
* will not happen with a nontransaction (although the connection may still
|
||||
* time out, e.g. when the network is unavailable for a very long time).
|
||||
*
|
||||
* Any query executed in a nontransaction is committed immediately, and neither
|
||||
* commit() nor abort() has any effect.
|
||||
*
|
||||
* Database features that require a backend transaction, such as cursors or
|
||||
* large objects, will not work in a nontransaction.
|
||||
*/
|
||||
class PQXX_LIBEXPORT nontransaction final : public transaction_base
|
||||
{
|
||||
public:
|
||||
/// Constructor.
|
||||
/** Create a "dummy" transaction.
|
||||
* @param c Connection in which this "transaction" will operate.
|
||||
* @param tname Optional tname for the transaction, beginning with a letter
|
||||
* and containing only letters and digits.
|
||||
*/
|
||||
nontransaction(connection &c, std::string_view tname = ""sv) :
|
||||
transaction_base{c, tname, std::shared_ptr<std::string>{}}
|
||||
{
|
||||
register_transaction();
|
||||
}
|
||||
|
||||
virtual ~nontransaction() override { close(); }
|
||||
|
||||
private:
|
||||
virtual void do_commit() override {}
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::notification_receiver functor interface.
|
||||
*
|
||||
* pqxx::notification_receiver handles incoming notifications.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/notification.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,94 @@
|
||||
/* Definition of the pqxx::notification_receiver functor interface.
|
||||
*
|
||||
* pqxx::notification_receiver handles incoming notifications.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/notification 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_NOTIFICATION
|
||||
#define PQXX_H_NOTIFICATION
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "pqxx/types.hxx"
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// "Observer" base class for notifications.
|
||||
/** @addtogroup notification Notifications and Receivers
|
||||
*
|
||||
* To listen on a notification issued using the NOTIFY command, derive your own
|
||||
* class from notification_receiver and define its function-call operator to
|
||||
* perform whatever action you wish to take when the given notification
|
||||
* arrives. Then create an object of that class and pass it to your connection.
|
||||
* DO NOT use raw SQL to listen for notifications, or your attempts to listen
|
||||
* won't be resumed when a connection fails--and you'll have no way to notice.
|
||||
*
|
||||
* Notifications never arrive inside a transaction, not even in a
|
||||
* nontransaction. Therefore, you are free to open a transaction of your own
|
||||
* inside your receiver's function invocation operator.
|
||||
*
|
||||
* Notifications you are listening for may arrive anywhere within libpqxx code,
|
||||
* but be aware that **PostgreSQL defers notifications occurring inside
|
||||
* transactions.** (This was done for excellent reasons; just think about what
|
||||
* happens if the transaction where you happen to handle an incoming
|
||||
* notification is later rolled back for other reasons). So if you're keeping
|
||||
* a transaction open, don't expect any of your receivers on the same
|
||||
* connection to be notified.
|
||||
*
|
||||
* (For very similar reasons, outgoing notifications are also not sent until
|
||||
* the transaction that sends them commits.)
|
||||
*
|
||||
* Multiple receivers on the same connection may listen on a notification of
|
||||
* the same name. An incoming notification is processed by invoking all
|
||||
* receivers (zero or more) of the same name.
|
||||
*/
|
||||
class PQXX_LIBEXPORT PQXX_NOVTABLE notification_receiver
|
||||
{
|
||||
public:
|
||||
/// Register the receiver with a connection.
|
||||
/**
|
||||
* @param c Connnection to operate on.
|
||||
* @param channel Name of the notification to listen for.
|
||||
*/
|
||||
notification_receiver(connection &c, std::string_view channel);
|
||||
/// Register the receiver with a connection.
|
||||
notification_receiver(notification_receiver const &) = delete;
|
||||
/// Register the receiver with a connection.
|
||||
notification_receiver &operator=(notification_receiver const &) = delete;
|
||||
/// Deregister the receiver.
|
||||
virtual ~notification_receiver();
|
||||
|
||||
/// The channel that this receiver listens on.
|
||||
[[nodiscard]] std::string const &channel() const & { return m_channel; }
|
||||
|
||||
// TODO: Change API to take payload as zview instead of string ref.
|
||||
/// Overridable: action to invoke when notification arrives.
|
||||
/**
|
||||
* @param payload An optional string that may have been passed to the NOTIFY
|
||||
* command.
|
||||
* @param backend_pid Process ID of the database backend process that served
|
||||
* our connection when the notification arrived. The actual process ID
|
||||
* behind the connection may have changed by the time this method is called.
|
||||
*/
|
||||
virtual void operator()(std::string const &payload, int backend_pid) = 0;
|
||||
|
||||
protected:
|
||||
connection &conn() const noexcept { return m_conn; }
|
||||
|
||||
private:
|
||||
connection &m_conn;
|
||||
std::string m_channel;
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** Helper classes for passing statement parameters.
|
||||
*
|
||||
* Use these for prepared statements and parameterised statements.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/params.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,383 @@
|
||||
/* Helpers for prepared statements and parameterised statements.
|
||||
*
|
||||
* See the connection class for more about such statements.
|
||||
*
|
||||
* 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_PARAMS
|
||||
#define PQXX_H_PARAMS
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "pqxx/internal/concat.hxx"
|
||||
#include "pqxx/internal/statement_parameters.hxx"
|
||||
#include "pqxx/types.hxx"
|
||||
|
||||
|
||||
/// @deprecated The new @ref params class replaces all of this.
|
||||
namespace pqxx::prepare
|
||||
{
|
||||
/// Pass a number of statement parameters only known at runtime.
|
||||
/** @deprecated Use @ref params instead.
|
||||
*
|
||||
* When you call any of the `exec_params` functions, the number of arguments
|
||||
* is normally known at compile time. This helper function supports the case
|
||||
* where it is not.
|
||||
*
|
||||
* Use this function to pass a variable number of parameters, based on a
|
||||
* sequence ranging from `begin` to `end` exclusively.
|
||||
*
|
||||
* The technique combines with the regular static parameters. You can use it
|
||||
* to insert dynamic parameter lists in any place, or places, among the call's
|
||||
* parameters. You can even insert multiple dynamic sequences.
|
||||
*
|
||||
* @param begin A pointer or iterator for iterating parameters.
|
||||
* @param end A pointer or iterator for iterating parameters.
|
||||
* @return An object representing the parameters.
|
||||
*/
|
||||
template<typename IT>
|
||||
[[deprecated("Use the params class instead.")]] constexpr inline auto
|
||||
make_dynamic_params(IT begin, IT end)
|
||||
{
|
||||
return pqxx::internal::dynamic_params(begin, end);
|
||||
}
|
||||
|
||||
|
||||
/// Pass a number of statement parameters only known at runtime.
|
||||
/** @deprecated Use @ref params instead.
|
||||
*
|
||||
* When you call any of the `exec_params` functions, the number of arguments
|
||||
* is normally known at compile time. This helper function supports the case
|
||||
* where it is not.
|
||||
*
|
||||
* Use this function to pass a variable number of parameters, based on a
|
||||
* container of parameter values.
|
||||
*
|
||||
* The technique combines with the regular static parameters. You can use it
|
||||
* to insert dynamic parameter lists in any place, or places, among the call's
|
||||
* parameters. You can even insert multiple dynamic containers.
|
||||
*
|
||||
* @param container A container of parameter values.
|
||||
* @return An object representing the parameters.
|
||||
*/
|
||||
template<typename C>
|
||||
[[deprecated("Use the params class instead.")]] constexpr inline auto
|
||||
make_dynamic_params(C const &container)
|
||||
{
|
||||
using IT = typename C::const_iterator;
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return pqxx::internal::dynamic_params<IT>{container};
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
|
||||
/// Pass a number of statement parameters only known at runtime.
|
||||
/** @deprecated Use @ref params instead.
|
||||
*
|
||||
* When you call any of the `exec_params` functions, the number of arguments
|
||||
* is normally known at compile time. This helper function supports the case
|
||||
* where it is not.
|
||||
*
|
||||
* Use this function to pass a variable number of parameters, based on a
|
||||
* container of parameter values.
|
||||
*
|
||||
* The technique combines with the regular static parameters. You can use it
|
||||
* to insert dynamic parameter lists in any place, or places, among the call's
|
||||
* parameters. You can even insert multiple dynamic containers.
|
||||
*
|
||||
* @param container A container of parameter values.
|
||||
* @param accessor For each parameter `p`, pass `accessor(p)`.
|
||||
* @return An object representing the parameters.
|
||||
*/
|
||||
template<typename C, typename ACCESSOR>
|
||||
[[deprecated("Use the params class instead.")]] constexpr inline auto
|
||||
make_dynamic_params(C &container, ACCESSOR accessor)
|
||||
{
|
||||
using IT = decltype(std::begin(container));
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return pqxx::internal::dynamic_params<IT, ACCESSOR>{container, accessor};
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
} // namespace pqxx::prepare
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Generate parameter placeholders for use in an SQL statement.
|
||||
/** When you want to pass parameters to a prepared statement or a parameterised
|
||||
* statement, you insert placeholders into the SQL. During invocation, the
|
||||
* database replaces those with the respective parameter values you passed.
|
||||
*
|
||||
* The placeholders look like `$1` (for the first parameter value), `$2` (for
|
||||
* the second), and so on. You can just write those directly in your
|
||||
* statement. But for those rare cases where it becomes difficult to track
|
||||
* which number a placeholder should have, you can use a `placeholders` object
|
||||
* to count and generate them in order.
|
||||
*/
|
||||
template<typename COUNTER = unsigned int> class placeholders
|
||||
{
|
||||
public:
|
||||
/// Maximum number of parameters we support.
|
||||
static inline constexpr unsigned int max_params{
|
||||
(std::numeric_limits<COUNTER>::max)()};
|
||||
|
||||
placeholders()
|
||||
{
|
||||
static constexpr auto initial{"$1\0"sv};
|
||||
initial.copy(std::data(m_buf), std::size(initial));
|
||||
}
|
||||
|
||||
/// Read an ephemeral version of the current placeholder text.
|
||||
/** @warning Changing the current placeholder number will overwrite this.
|
||||
* Use the view immediately, or lose it.
|
||||
*/
|
||||
constexpr zview view() const &noexcept
|
||||
{
|
||||
return zview{std::data(m_buf), m_len};
|
||||
}
|
||||
|
||||
/// Read the current placeholder text, as a `std::string`.
|
||||
/** This will be slightly slower than converting to a `zview`. With most
|
||||
* C++ implementations however, until you get into ridiculous numbers of
|
||||
* parameters, the string will benefit from the Short String Optimization, or
|
||||
* SSO.
|
||||
*/
|
||||
std::string get() const { return std::string(std::data(m_buf), m_len); }
|
||||
|
||||
/// Move on to the next parameter.
|
||||
void next() &
|
||||
{
|
||||
if (m_current >= max_params)
|
||||
throw range_error{pqxx::internal::concat(
|
||||
"Too many parameters in one statement: limit is ", max_params, ".")};
|
||||
++m_current;
|
||||
if (m_current % 10 == 0)
|
||||
{
|
||||
// Carry the 1. Don't get too clever for this relatively rare
|
||||
// case, just rewrite the entire number. Leave the $ in place
|
||||
// though.
|
||||
char *const data{std::data(m_buf)};
|
||||
char *const end{string_traits<COUNTER>::into_buf(
|
||||
data + 1, data + std::size(m_buf), m_current)};
|
||||
// (Subtract because we don't include the trailing zero.)
|
||||
m_len = check_cast<COUNTER>(end - data, "placeholders counter") - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
PQXX_LIKELY
|
||||
// Shortcut for the common case: just increment that last digit.
|
||||
++m_buf[m_len - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the current placeholder number. The initial placeholder is 1.
|
||||
COUNTER count() const noexcept { return m_current; }
|
||||
|
||||
private:
|
||||
/// Current placeholder number. Starts at 1.
|
||||
COUNTER m_current = 1;
|
||||
|
||||
/// Length of the current placeholder string, not including trailing zero.
|
||||
COUNTER m_len = 2;
|
||||
|
||||
/// Text buffer where we render the placeholders, with a trailing zero.
|
||||
/** We keep reusing this for every subsequent placeholder, just because we
|
||||
* don't like string allocations.
|
||||
*
|
||||
* Maximum length is the maximum base-10 digits that COUNTER can fully
|
||||
* represent, plus 1 more for the extra digit that it can only partially
|
||||
* fill up, plus room for the dollar sign and the trailing zero.
|
||||
*/
|
||||
std::array<char, std::numeric_limits<COUNTER>::digits10 + 3> m_buf;
|
||||
};
|
||||
|
||||
|
||||
/// Build a parameter list for a parameterised or prepared statement.
|
||||
/** When calling a parameterised statement or a prepared statement, you can
|
||||
* pass parameters into the statement directly in the invocation, as
|
||||
* additional arguments to `exec_prepared` or `exec_params`. But in
|
||||
* complex cases, sometimes that's just not convenient.
|
||||
*
|
||||
* In those situations, you can create a `params` and append your parameters
|
||||
* into that, one by one. Then you pass the `params` to `exec_prepared` or
|
||||
* `exec_params`.
|
||||
*
|
||||
* Combinations also work: if you have a `params` containing a string
|
||||
* parameter, and you call `exec_params` with an `int` argument followed by
|
||||
* your `params`, you'll be passing the `int` as the first parameter and
|
||||
* the string as the second. You can even insert a `params` in a `params`,
|
||||
* or pass two `params` objects to a statement.
|
||||
*/
|
||||
class PQXX_LIBEXPORT params
|
||||
{
|
||||
public:
|
||||
params() = default;
|
||||
|
||||
/// Pre-populate a `params` with `args`. Feel free to add more later.
|
||||
template<typename... Args> constexpr params(Args &&...args)
|
||||
{
|
||||
reserve(sizeof...(args));
|
||||
append_pack(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Pre-allocate room for at least `n` parameters.
|
||||
/** This is not needed, but it may improve efficiency.
|
||||
*
|
||||
* Reserve space if you're going to add parameters individually, and you've
|
||||
* got some idea of how many there are going to be. It may save some
|
||||
* memory re-allocations.
|
||||
*/
|
||||
void reserve(std::size_t n) &;
|
||||
|
||||
// C++20: constexpr.
|
||||
/// Get the number of parameters currently in this `params`.
|
||||
[[nodiscard]] auto size() const noexcept { return m_params.size(); }
|
||||
|
||||
// C++20: Use the vector's ssize() directly and go noexcept+constexpr.
|
||||
/// Get the number of parameters (signed).
|
||||
/** Unlike `size()`, this is not yet `noexcept`. That's because C++17's
|
||||
* `std::vector` does not have a `ssize()` member function. These member
|
||||
* functions are `noexcept`, but `std::size()` and `std::ssize()` are
|
||||
* not.
|
||||
*/
|
||||
[[nodiscard]] auto ssize() const { return pqxx::internal::ssize(m_params); }
|
||||
|
||||
/// Append a null value.
|
||||
void append() &;
|
||||
|
||||
/// Append a non-null zview parameter.
|
||||
/** The underlying data must stay valid for as long as the `params`
|
||||
* remains active.
|
||||
*/
|
||||
void append(zview) &;
|
||||
|
||||
/// Append a non-null string parameter.
|
||||
/** Copies the underlying data into internal storage. For best efficiency,
|
||||
* use the @ref zview variant if you can, or `std::move()`
|
||||
*/
|
||||
void append(std::string const &) &;
|
||||
|
||||
/// Append a non-null string parameter.
|
||||
void append(std::string &&) &;
|
||||
|
||||
/// Append a non-null binary parameter.
|
||||
/** The underlying data must stay valid for as long as the `params`
|
||||
* remains active.
|
||||
*/
|
||||
void append(std::basic_string_view<std::byte>) &;
|
||||
|
||||
/// Append a non-null binary parameter.
|
||||
/** Copies the underlying data into internal storage. For best efficiency,
|
||||
* use the `std::basic_string_view<std::byte>` variant if you can, or
|
||||
* `std::move()`.
|
||||
*/
|
||||
void append(std::basic_string<std::byte> const &) &;
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
/// Append a non-null binary parameter.
|
||||
/** The `data` object must stay in place and unchanged, for as long as the
|
||||
* `params` remains active.
|
||||
*/
|
||||
template<binary DATA> void append(DATA const &data) &
|
||||
{
|
||||
append(
|
||||
std::basic_string_view<std::byte>{std::data(data), std::size(data)});
|
||||
}
|
||||
#endif // PQXX_HAVE_CONCEPTS
|
||||
|
||||
/// Append a non-null binary parameter.
|
||||
void append(std::basic_string<std::byte> &&) &;
|
||||
|
||||
/// @deprecated Append binarystring parameter.
|
||||
/** The binarystring must stay valid for as long as the `params` remains
|
||||
* active.
|
||||
*/
|
||||
void append(binarystring const &value) &;
|
||||
|
||||
/// Append all parameters from value.
|
||||
template<typename IT, typename ACCESSOR>
|
||||
void append(pqxx::internal::dynamic_params<IT, ACCESSOR> const &value) &
|
||||
{
|
||||
for (auto ¶m : value) append(value.access(param));
|
||||
}
|
||||
|
||||
void append(params const &value) &;
|
||||
|
||||
void append(params &&value) &;
|
||||
|
||||
/// Append a non-null parameter, converting it to its string
|
||||
/// representation.
|
||||
template<typename TYPE> void append(TYPE const &value) &
|
||||
{
|
||||
// TODO: Pool storage for multiple string conversions in one buffer?
|
||||
if constexpr (nullness<strip_t<TYPE>>::always_null)
|
||||
{
|
||||
ignore_unused(value);
|
||||
m_params.emplace_back();
|
||||
}
|
||||
else if (is_null(value))
|
||||
{
|
||||
m_params.emplace_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_params.emplace_back(entry{to_string(value)});
|
||||
}
|
||||
}
|
||||
|
||||
/// Append all elements of `range` as parameters.
|
||||
template<PQXX_RANGE_ARG RANGE> void append_multi(RANGE const &range) &
|
||||
{
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
if constexpr (std::ranges::sized_range<RANGE>)
|
||||
reserve(std::size(*this) + std::size(range));
|
||||
#endif
|
||||
for (auto &value : range) append(value);
|
||||
}
|
||||
|
||||
/// For internal use: Generate a `params` object for use in calls.
|
||||
/** The params object encapsulates the pointers which we will need to pass
|
||||
* to libpq when calling a parameterised or prepared statement.
|
||||
*
|
||||
* The pointers in the params will refer to storage owned by either the
|
||||
* params object, or the caller. This is not a problem because a
|
||||
* `c_params` object is guaranteed to live only while the call is going on.
|
||||
* As soon as we climb back out of that call tree, we're done with that
|
||||
* data.
|
||||
*/
|
||||
pqxx::internal::c_params make_c_params() const;
|
||||
|
||||
private:
|
||||
/// Recursively append a pack of params.
|
||||
template<typename Arg, typename... More>
|
||||
void append_pack(Arg &&arg, More &&...args)
|
||||
{
|
||||
this->append(std::forward<Arg>(arg));
|
||||
// Recurse for remaining args.
|
||||
append_pack(std::forward<More>(args)...);
|
||||
}
|
||||
|
||||
/// Terminating case: append an empty parameter pack. It's not hard BTW.
|
||||
constexpr void append_pack() noexcept {}
|
||||
|
||||
// The way we store a parameter depends on whether it's binary or text
|
||||
// (most types are text), and whether we're responsible for storing the
|
||||
// contents.
|
||||
using entry = std::variant<
|
||||
std::nullptr_t, zview, std::string, std::basic_string_view<std::byte>,
|
||||
std::basic_string<std::byte>>;
|
||||
std::vector<entry> m_params;
|
||||
|
||||
static constexpr std::string_view s_overflow{
|
||||
"Statement parameter length overflow."sv};
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::pipeline class.
|
||||
*
|
||||
* Throughput-optimized query interface.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/pipeline.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,237 @@
|
||||
/* 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
|
||||
@@ -0,0 +1,28 @@
|
||||
/// Convenience header: include all libpqxx definitions.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
|
||||
#include "pqxx/array.hxx"
|
||||
#include "pqxx/binarystring.hxx"
|
||||
#include "pqxx/blob.hxx"
|
||||
#include "pqxx/connection.hxx"
|
||||
#include "pqxx/cursor.hxx"
|
||||
#include "pqxx/errorhandler.hxx"
|
||||
#include "pqxx/except.hxx"
|
||||
#include "pqxx/largeobject.hxx"
|
||||
#include "pqxx/nontransaction.hxx"
|
||||
#include "pqxx/notification.hxx"
|
||||
#include "pqxx/params.hxx"
|
||||
#include "pqxx/pipeline.hxx"
|
||||
#include "pqxx/prepared_statement.hxx"
|
||||
#include "pqxx/result.hxx"
|
||||
#include "pqxx/internal/result_iterator.hxx"
|
||||
#include "pqxx/internal/result_iter.hxx"
|
||||
#include "pqxx/robusttransaction.hxx"
|
||||
#include "pqxx/row.hxx"
|
||||
#include "pqxx/stream_from.hxx"
|
||||
#include "pqxx/stream_to.hxx"
|
||||
#include "pqxx/subtransaction.hxx"
|
||||
#include "pqxx/transaction.hxx"
|
||||
#include "pqxx/transactor.hxx"
|
||||
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,3 @@
|
||||
/// @deprecated Include @c <pqxx/params> instead.
|
||||
|
||||
#include "params.hxx"
|
||||
@@ -0,0 +1,3 @@
|
||||
/// @deprecated Include @c <pqxx/params> instead.
|
||||
|
||||
#include "params.hxx"
|
||||
@@ -0,0 +1,6 @@
|
||||
/** Client-side support for SQL range types.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/range.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,515 @@
|
||||
#ifndef PQXX_H_RANGE
|
||||
#define PQXX_H_RANGE
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "pqxx/internal/array-composite.hxx"
|
||||
#include "pqxx/internal/concat.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// An _unlimited_ boundary value to a @ref pqxx::range.
|
||||
/** Use this as a lower or upper bound for a range if the range should extend
|
||||
* to infinity on that side.
|
||||
*
|
||||
* An unlimited boundary is always inclusive of "infinity" values, if the
|
||||
* range's value type supports them.
|
||||
*/
|
||||
struct no_bound
|
||||
{
|
||||
template<typename TYPE> constexpr bool extends_down_to(TYPE const &) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
template<typename TYPE> constexpr bool extends_up_to(TYPE const &) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// An _inclusive_ boundary value to a @ref pqxx::range.
|
||||
/** Use this as a lower or upper bound for a range if the range should include
|
||||
* the value.
|
||||
*/
|
||||
template<typename TYPE> class inclusive_bound
|
||||
{
|
||||
public:
|
||||
inclusive_bound() = delete;
|
||||
explicit inclusive_bound(TYPE const &value) : m_value{value}
|
||||
{
|
||||
if (is_null(value))
|
||||
throw argument_error{"Got null value as an inclusive range bound."};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; }
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operator supports it.
|
||||
/// Would this bound, as a lower bound, include value?
|
||||
[[nodiscard]] bool extends_down_to(TYPE const &value) const
|
||||
{
|
||||
return not(value < m_value);
|
||||
}
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operator supports it.
|
||||
/// Would this bound, as an upper bound, include value?
|
||||
[[nodiscard]] bool extends_up_to(TYPE const &value) const
|
||||
{
|
||||
return not(m_value < value);
|
||||
}
|
||||
|
||||
private:
|
||||
TYPE m_value;
|
||||
};
|
||||
|
||||
|
||||
/// An _exclusive_ boundary value to a @ref pqxx::range.
|
||||
/** Use this as a lower or upper bound for a range if the range should _not_
|
||||
* include the value.
|
||||
*/
|
||||
template<typename TYPE> class exclusive_bound
|
||||
{
|
||||
public:
|
||||
exclusive_bound() = delete;
|
||||
explicit exclusive_bound(TYPE const &value) : m_value{value}
|
||||
{
|
||||
if (is_null(value))
|
||||
throw argument_error{"Got null value as an exclusive range bound."};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; }
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operator supports it.
|
||||
/// Would this bound, as a lower bound, include value?
|
||||
[[nodiscard]] bool extends_down_to(TYPE const &value) const
|
||||
{
|
||||
return m_value < value;
|
||||
}
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operator supports it.
|
||||
/// Would this bound, as an upper bound, include value?
|
||||
[[nodiscard]] bool extends_up_to(TYPE const &value) const
|
||||
{
|
||||
return value < m_value;
|
||||
}
|
||||
|
||||
private:
|
||||
TYPE m_value;
|
||||
};
|
||||
|
||||
|
||||
/// A range boundary value.
|
||||
/** A range bound is either no bound at all; or an inclusive bound; or an
|
||||
* exclusive bound. Pass one of the three to the constructor.
|
||||
*/
|
||||
template<typename TYPE> class range_bound
|
||||
{
|
||||
public:
|
||||
range_bound() = delete;
|
||||
// TODO: constexpr and/or noexcept if underlying constructor supports it.
|
||||
range_bound(no_bound) : m_bound{} {}
|
||||
// TODO: constexpr and/or noexcept if underlying constructor supports it.
|
||||
range_bound(inclusive_bound<TYPE> const &bound) : m_bound{bound} {}
|
||||
// TODO: constexpr and/or noexcept if underlying constructor supports it.
|
||||
range_bound(exclusive_bound<TYPE> const &bound) : m_bound{bound} {}
|
||||
// TODO: constexpr and/or noexcept if underlying constructor supports it.
|
||||
range_bound(range_bound const &) = default;
|
||||
// TODO: constexpr and/or noexcept if underlying constructor supports it.
|
||||
range_bound(range_bound &&) = default;
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operators support it.
|
||||
bool operator==(range_bound const &rhs) const
|
||||
{
|
||||
if (this->is_limited())
|
||||
return (
|
||||
rhs.is_limited() and (this->is_inclusive() == rhs.is_inclusive()) and
|
||||
(*this->value() == *rhs.value()));
|
||||
else
|
||||
return not rhs.is_limited();
|
||||
}
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operator supports it.
|
||||
bool operator!=(range_bound const &rhs) const { return not(*this == rhs); }
|
||||
range_bound &operator=(range_bound const &) = default;
|
||||
range_bound &operator=(range_bound &&) = default;
|
||||
|
||||
/// Is this a finite bound?
|
||||
constexpr bool is_limited() const noexcept
|
||||
{
|
||||
return not std::holds_alternative<no_bound>(m_bound);
|
||||
}
|
||||
|
||||
/// Is this boundary an inclusive one?
|
||||
constexpr bool is_inclusive() const noexcept
|
||||
{
|
||||
return std::holds_alternative<inclusive_bound<TYPE>>(m_bound);
|
||||
}
|
||||
|
||||
/// Is this boundary an exclusive one?
|
||||
constexpr bool is_exclusive() const noexcept
|
||||
{
|
||||
return std::holds_alternative<exclusive_bound<TYPE>>(m_bound);
|
||||
}
|
||||
|
||||
// TODO: constexpr/noexcept if underlying function supports it.
|
||||
/// Would this bound, as a lower bound, include `value`?
|
||||
bool extends_down_to(TYPE const &value) const
|
||||
{
|
||||
return std::visit(
|
||||
[&value](auto const &bound) { return bound.extends_down_to(value); },
|
||||
m_bound);
|
||||
}
|
||||
|
||||
// TODO: constexpr/noexcept if underlying function supports it.
|
||||
/// Would this bound, as an upper bound, include `value`?
|
||||
bool extends_up_to(TYPE const &value) const
|
||||
{
|
||||
return std::visit(
|
||||
[&value](auto const &bound) { return bound.extends_up_to(value); },
|
||||
m_bound);
|
||||
}
|
||||
|
||||
/// Return bound value, or `nullptr` if it's not limited.
|
||||
[[nodiscard]] constexpr TYPE const *value() const &noexcept
|
||||
{
|
||||
return std::visit(
|
||||
[](auto const &bound) noexcept {
|
||||
using bound_t = std::decay_t<decltype(bound)>;
|
||||
if constexpr (std::is_same_v<bound_t, no_bound>)
|
||||
return static_cast<TYPE const *>(nullptr);
|
||||
else
|
||||
return &bound.get();
|
||||
},
|
||||
m_bound);
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<no_bound, inclusive_bound<TYPE>, exclusive_bound<TYPE>> m_bound;
|
||||
};
|
||||
|
||||
|
||||
// C++20: Concepts for comparisons, construction, etc.
|
||||
/// A C++ equivalent to PostgreSQL's range types.
|
||||
/** You can use this as a client-side representation of a "range" in SQL.
|
||||
*
|
||||
* PostgreSQL defines several range types, differing in the data type over
|
||||
* which they range. You can also define your own range types.
|
||||
*
|
||||
* Usually you'll want the server to deal with ranges. But on occasions where
|
||||
* you need to work with them client-side, you may want to use @ref
|
||||
* pqxx::range. (In cases where all you do is pass them along to the server
|
||||
* though, it's not worth the complexity. In that case you might as well treat
|
||||
* ranges as just strings.)
|
||||
*
|
||||
* For documentation on PostgreSQL's range types, see:
|
||||
* https://www.postgresql.org/docs/current/rangetypes.html
|
||||
*
|
||||
* The value type must be copyable and default-constructible, and support the
|
||||
* less-than (`<`) and equals (`==`) comparisons. Value initialisation must
|
||||
* produce a consistent value.
|
||||
*/
|
||||
template<typename TYPE> class range
|
||||
{
|
||||
public:
|
||||
/// Create a range.
|
||||
/** For each of the two bounds, pass a @ref no_bound, @ref inclusive_bound,
|
||||
* or
|
||||
* @ref exclusive_bound.
|
||||
*/
|
||||
range(range_bound<TYPE> lower, range_bound<TYPE> upper) :
|
||||
m_lower{lower}, m_upper{upper}
|
||||
{
|
||||
if (
|
||||
lower.is_limited() and upper.is_limited() and
|
||||
(*upper.value() < *lower.value()))
|
||||
throw range_error{internal::concat(
|
||||
"Range's lower bound (", *lower.value(),
|
||||
") is greater than its upper bound (", *upper.value(), ").")};
|
||||
}
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying constructor supports it.
|
||||
/// Create an empty range.
|
||||
/** SQL has a separate literal to denote an empty range, but any range which
|
||||
* encompasses no values is an empty range.
|
||||
*/
|
||||
range() :
|
||||
m_lower{exclusive_bound<TYPE>{TYPE{}}},
|
||||
m_upper{exclusive_bound<TYPE>{TYPE{}}}
|
||||
{}
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operators support it.
|
||||
bool operator==(range const &rhs) const
|
||||
{
|
||||
return (this->lower_bound() == rhs.lower_bound() and
|
||||
this->upper_bound() == rhs.upper_bound()) or
|
||||
(this->empty() and rhs.empty());
|
||||
}
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operator supports it.
|
||||
bool operator!=(range const &rhs) const { return !(*this == rhs); }
|
||||
|
||||
range(range const &) = default;
|
||||
range(range &&) = default;
|
||||
range &operator=(range const &) = default;
|
||||
range &operator=(range &&) = default;
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operator supports it.
|
||||
/// Is this range clearly empty?
|
||||
/** An empty range encompasses no values.
|
||||
*
|
||||
* It is possible to "fool" this. For example, if your range is of an
|
||||
* integer type and has exclusive bounds of 0 and 1, it encompasses no values
|
||||
* but its `empty()` will return false. The PostgreSQL implementation, by
|
||||
* contrast, will notice that it is empty. Similar things can happen for
|
||||
* floating-point types, but with more subtleties and edge cases.
|
||||
*/
|
||||
bool empty() const
|
||||
{
|
||||
return (m_lower.is_exclusive() or m_upper.is_exclusive()) and
|
||||
m_lower.is_limited() and m_upper.is_limited() and
|
||||
not(*m_lower.value() < *m_upper.value());
|
||||
}
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying functions support it.
|
||||
/// Does this range encompass `value`?
|
||||
bool contains(TYPE value) const
|
||||
{
|
||||
return m_lower.extends_down_to(value) and m_upper.extends_up_to(value);
|
||||
}
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operators support it.
|
||||
/// Does this range encompass all of `other`?
|
||||
/** This function is not particularly smart. It does not know, for example,
|
||||
* that integer ranges `[0,9]` and `[0,10)` contain the same values.
|
||||
*/
|
||||
bool contains(range<TYPE> const &other) const
|
||||
{
|
||||
return (*this & other) == other;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr range_bound<TYPE> const &
|
||||
lower_bound() const &noexcept
|
||||
{
|
||||
return m_lower;
|
||||
}
|
||||
[[nodiscard]] constexpr range_bound<TYPE> const &
|
||||
upper_bound() const &noexcept
|
||||
{
|
||||
return m_upper;
|
||||
}
|
||||
|
||||
// TODO: constexpr and/or noexcept if underlying operators support it.
|
||||
/// Intersection of two ranges.
|
||||
/** Returns a range describing those values which are in both ranges.
|
||||
*/
|
||||
range operator&(range const &other) const
|
||||
{
|
||||
range_bound<TYPE> lower{no_bound{}};
|
||||
if (not this->lower_bound().is_limited())
|
||||
lower = other.lower_bound();
|
||||
else if (not other.lower_bound().is_limited())
|
||||
lower = this->lower_bound();
|
||||
else if (*this->lower_bound().value() < *other.lower_bound().value())
|
||||
lower = other.lower_bound();
|
||||
else if (*other.lower_bound().value() < *this->lower_bound().value())
|
||||
lower = this->lower_bound();
|
||||
else if (this->lower_bound().is_exclusive())
|
||||
lower = this->lower_bound();
|
||||
else
|
||||
lower = other.lower_bound();
|
||||
|
||||
range_bound<TYPE> upper{no_bound{}};
|
||||
if (not this->upper_bound().is_limited())
|
||||
upper = other.upper_bound();
|
||||
else if (not other.upper_bound().is_limited())
|
||||
upper = this->upper_bound();
|
||||
else if (*other.upper_bound().value() < *this->upper_bound().value())
|
||||
upper = other.upper_bound();
|
||||
else if (*this->upper_bound().value() < *other.upper_bound().value())
|
||||
upper = this->upper_bound();
|
||||
else if (this->upper_bound().is_exclusive())
|
||||
upper = this->upper_bound();
|
||||
else
|
||||
upper = other.upper_bound();
|
||||
|
||||
if (
|
||||
lower.is_limited() and upper.is_limited() and
|
||||
(*upper.value() < *lower.value()))
|
||||
return {};
|
||||
else
|
||||
return {lower, upper};
|
||||
}
|
||||
|
||||
/// Convert to another base type.
|
||||
template<typename DEST> operator range<DEST>() const
|
||||
{
|
||||
range_bound<DEST> lower{no_bound{}}, upper{no_bound{}};
|
||||
if (lower_bound().is_inclusive())
|
||||
lower = inclusive_bound<DEST>{*lower_bound().value()};
|
||||
else if (lower_bound().is_exclusive())
|
||||
lower = exclusive_bound<DEST>{*lower_bound().value()};
|
||||
|
||||
if (upper_bound().is_inclusive())
|
||||
upper = inclusive_bound<DEST>{*upper_bound().value()};
|
||||
else if (upper_bound().is_exclusive())
|
||||
upper = exclusive_bound<DEST>{*upper_bound().value()};
|
||||
|
||||
return {lower, upper};
|
||||
}
|
||||
|
||||
private:
|
||||
range_bound<TYPE> m_lower, m_upper;
|
||||
};
|
||||
|
||||
|
||||
/// String conversions for a @ref range type.
|
||||
/** Conversion assumes that either your client encoding is UTF-8, or the values
|
||||
* are pure ASCII.
|
||||
*/
|
||||
template<typename TYPE> struct string_traits<range<TYPE>>
|
||||
{
|
||||
[[nodiscard]] static inline zview
|
||||
to_buf(char *begin, char *end, range<TYPE> const &value)
|
||||
{
|
||||
return generic_to_buf(begin, end, value);
|
||||
}
|
||||
|
||||
static inline char *
|
||||
into_buf(char *begin, char *end, range<TYPE> const &value)
|
||||
{
|
||||
if (value.empty())
|
||||
{
|
||||
if ((end - begin) <= internal::ssize(s_empty))
|
||||
throw conversion_overrun{s_overrun.c_str()};
|
||||
char *here = begin + s_empty.copy(begin, std::size(s_empty));
|
||||
*here++ = '\0';
|
||||
return here;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (end - begin < 4)
|
||||
throw conversion_overrun{s_overrun.c_str()};
|
||||
char *here = begin;
|
||||
*here++ =
|
||||
(static_cast<char>(value.lower_bound().is_inclusive() ? '[' : '('));
|
||||
TYPE const *lower{value.lower_bound().value()};
|
||||
// Convert bound (but go back to overwrite that trailing zero).
|
||||
if (lower != nullptr)
|
||||
here = string_traits<TYPE>::into_buf(here, end, *lower) - 1;
|
||||
*here++ = ',';
|
||||
TYPE const *upper{value.upper_bound().value()};
|
||||
// Convert bound (but go back to overwrite that trailing zero).
|
||||
if (upper != nullptr)
|
||||
here = string_traits<TYPE>::into_buf(here, end, *upper) - 1;
|
||||
if ((end - here) < 2)
|
||||
throw conversion_overrun{s_overrun.c_str()};
|
||||
*here++ =
|
||||
static_cast<char>(value.upper_bound().is_inclusive() ? ']' : ')');
|
||||
*here++ = '\0';
|
||||
return here;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] static inline range<TYPE> from_string(std::string_view text)
|
||||
{
|
||||
if (std::size(text) < 3)
|
||||
throw pqxx::conversion_error{err_bad_input(text)};
|
||||
bool left_inc{false};
|
||||
switch (text[0])
|
||||
{
|
||||
case '[': left_inc = true; break;
|
||||
|
||||
case '(': break;
|
||||
|
||||
case 'e':
|
||||
case 'E':
|
||||
if (
|
||||
(std::size(text) != std::size(s_empty)) or
|
||||
(text[1] != 'm' and text[1] != 'M') or
|
||||
(text[2] != 'p' and text[2] != 'P') or
|
||||
(text[3] != 't' and text[3] != 'T') or
|
||||
(text[4] != 'y' and text[4] != 'Y'))
|
||||
throw pqxx::conversion_error{err_bad_input(text)};
|
||||
return {};
|
||||
break;
|
||||
|
||||
default: throw pqxx::conversion_error{err_bad_input(text)};
|
||||
}
|
||||
|
||||
auto scan{internal::get_glyph_scanner(internal::encoding_group::UTF8)};
|
||||
// The field parser uses this to track which field it's parsing, and
|
||||
// when not to expect a field separator.
|
||||
std::size_t index{0};
|
||||
// The last field we expect to see.
|
||||
static constexpr std::size_t last{1};
|
||||
// Current parsing position. We skip the opening parenthesis or bracket.
|
||||
std::size_t pos{1};
|
||||
// The string may leave out either bound to indicate that it's unlimited.
|
||||
std::optional<TYPE> lower, upper;
|
||||
// We reuse the same field parser we use for composite values and arrays.
|
||||
internal::parse_composite_field(index, text, pos, lower, scan, last);
|
||||
internal::parse_composite_field(index, text, pos, upper, scan, last);
|
||||
|
||||
// We need one more character: the closing parenthesis or bracket.
|
||||
if (pos != std::size(text))
|
||||
throw pqxx::conversion_error{err_bad_input(text)};
|
||||
char const closing{text[pos - 1]};
|
||||
if (closing != ')' and closing != ']')
|
||||
throw pqxx::conversion_error{err_bad_input(text)};
|
||||
bool const right_inc{closing == ']'};
|
||||
|
||||
range_bound<TYPE> lower_bound{no_bound{}}, upper_bound{no_bound{}};
|
||||
if (lower)
|
||||
{
|
||||
if (left_inc)
|
||||
lower_bound = inclusive_bound{*lower};
|
||||
else
|
||||
lower_bound = exclusive_bound{*lower};
|
||||
}
|
||||
if (upper)
|
||||
{
|
||||
if (right_inc)
|
||||
upper_bound = inclusive_bound{*upper};
|
||||
else
|
||||
upper_bound = exclusive_bound{*upper};
|
||||
}
|
||||
|
||||
return {lower_bound, upper_bound};
|
||||
}
|
||||
|
||||
[[nodiscard]] static inline constexpr std::size_t
|
||||
size_buffer(range<TYPE> const &value) noexcept
|
||||
{
|
||||
TYPE const *lower{value.lower_bound().value()},
|
||||
*upper{value.upper_bound().value()};
|
||||
std::size_t const lsz{
|
||||
lower == nullptr ? 0 : string_traits<TYPE>::size_buffer(*lower) - 1},
|
||||
usz{upper == nullptr ? 0 : string_traits<TYPE>::size_buffer(*upper) - 1};
|
||||
|
||||
if (value.empty())
|
||||
return std::size(s_empty) + 1;
|
||||
else
|
||||
return 1 + lsz + 1 + usz + 2;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr zview s_empty{"empty"_zv};
|
||||
static constexpr auto s_overrun{"Not enough space in buffer for range."_zv};
|
||||
|
||||
/// Compose error message for invalid range input.
|
||||
static std::string err_bad_input(std::string_view text)
|
||||
{
|
||||
return internal::concat("Invalid range input: '", text, "'");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// A range type does not have an innate null value.
|
||||
template<typename TYPE> struct nullness<range<TYPE>> : no_null<range<TYPE>>
|
||||
{};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
/** pqxx::result class and support classes.
|
||||
*
|
||||
* pqxx::result represents the set of result rows from a database query.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
|
||||
#include "pqxx/result.hxx"
|
||||
|
||||
// Now include some types which depend on result, but which the user will
|
||||
// expect to see defined after including this header.
|
||||
#include "pqxx/internal/result_iterator.hxx"
|
||||
#include "pqxx/field.hxx"
|
||||
#include "pqxx/internal/result_iter.hxx"
|
||||
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,335 @@
|
||||
/* Definitions for the pqxx::result class and support classes.
|
||||
*
|
||||
* pqxx::result represents the set of result rows from a database query.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result 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_RESULT
|
||||
#define PQXX_H_RESULT
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include <ios>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "pqxx/except.hxx"
|
||||
#include "pqxx/types.hxx"
|
||||
#include "pqxx/util.hxx"
|
||||
#include "pqxx/zview.hxx"
|
||||
|
||||
#include "pqxx/internal/encodings.hxx"
|
||||
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
// TODO: Make noexcept (but breaks ABI).
|
||||
PQXX_LIBEXPORT void clear_result(pq::PGresult const *);
|
||||
} // namespace pqxx::internal
|
||||
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class result_connection;
|
||||
class result_creation;
|
||||
class result_pipeline;
|
||||
class result_row;
|
||||
class result_sql_cursor;
|
||||
} // namespace pqxx::internal::gate
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Result set containing data returned by a query or command.
|
||||
/** This behaves as a container (as defined by the C++ standard library) and
|
||||
* provides random access const iterators to iterate over its rows. You can
|
||||
* also access a row by indexing a `result R` by the row's zero-based
|
||||
* number:
|
||||
*
|
||||
*
|
||||
* for (result::size_type i=0; i < std::size(R); ++i) Process(R[i]);
|
||||
*
|
||||
*
|
||||
* Result sets in libpqxx are lightweight, reference-counted wrapper objects
|
||||
* which are relatively small and cheap to copy. Think of a result object as
|
||||
* a "smart pointer" to an underlying result set.
|
||||
*
|
||||
* @warning The result set that a result object points to is not thread-safe.
|
||||
* If you copy a result object, it still refers to the same underlying result
|
||||
* set. So never copy, destroy, query, or otherwise access a result while
|
||||
* another thread may be copying, destroying, querying, or otherwise accessing
|
||||
* the same result set--even if it is doing so through a different result
|
||||
* object!
|
||||
*/
|
||||
class PQXX_LIBEXPORT result
|
||||
{
|
||||
public:
|
||||
using size_type = result_size_type;
|
||||
using difference_type = result_difference_type;
|
||||
using reference = row;
|
||||
using const_iterator = const_result_iterator;
|
||||
using pointer = const_iterator;
|
||||
using iterator = const_iterator;
|
||||
using const_reverse_iterator = const_reverse_result_iterator;
|
||||
using reverse_iterator = const_reverse_iterator;
|
||||
|
||||
result() noexcept :
|
||||
m_data{make_data_pointer()},
|
||||
m_query{},
|
||||
m_encoding{internal::encoding_group::MONOBYTE}
|
||||
{}
|
||||
|
||||
result(result const &rhs) noexcept = default;
|
||||
result(result &&rhs) noexcept = default;
|
||||
|
||||
/// Assign one result to another.
|
||||
/** Copying results is cheap: it copies only smart pointers, but the actual
|
||||
* data stays in the same place.
|
||||
*/
|
||||
result &operator=(result const &rhs) noexcept = default;
|
||||
|
||||
/// Assign one result to another, invaliding the old one.
|
||||
result &operator=(result &&rhs) noexcept = default;
|
||||
|
||||
/**
|
||||
* @name Comparisons
|
||||
*
|
||||
* You can compare results for equality. Beware: this is a very strict,
|
||||
* dumb comparison. The smallest difference between two results (such as a
|
||||
* string "Foo" versus a string "foo") will make them unequal.
|
||||
*/
|
||||
//@{
|
||||
/// Compare two results for equality.
|
||||
[[nodiscard]] bool operator==(result const &) const noexcept;
|
||||
/// Compare two results for inequality.
|
||||
[[nodiscard]] bool operator!=(result const &rhs) const noexcept
|
||||
{
|
||||
return not operator==(rhs);
|
||||
}
|
||||
//@}
|
||||
|
||||
/// Iterate rows, reading them directly into a tuple of "TYPE...".
|
||||
/** Converts the fields to values of the given respective types.
|
||||
*
|
||||
* Use this only with a ranged "for" loop. The iteration produces
|
||||
* std::tuple<TYPE...> which you can "unpack" to a series of `auto`
|
||||
* variables.
|
||||
*/
|
||||
template<typename... TYPE> auto iter() const;
|
||||
|
||||
[[nodiscard]] const_reverse_iterator rbegin() const;
|
||||
[[nodiscard]] const_reverse_iterator crbegin() const;
|
||||
[[nodiscard]] const_reverse_iterator rend() const;
|
||||
[[nodiscard]] const_reverse_iterator crend() const;
|
||||
|
||||
[[nodiscard]] const_iterator begin() const noexcept;
|
||||
[[nodiscard]] const_iterator cbegin() const noexcept;
|
||||
[[nodiscard]] inline const_iterator end() const noexcept;
|
||||
[[nodiscard]] inline const_iterator cend() const noexcept;
|
||||
|
||||
[[nodiscard]] reference front() const noexcept;
|
||||
[[nodiscard]] reference back() const noexcept;
|
||||
|
||||
[[nodiscard]] PQXX_PURE size_type size() const noexcept;
|
||||
[[nodiscard]] PQXX_PURE bool empty() const noexcept;
|
||||
[[nodiscard]] size_type capacity() const noexcept { return size(); }
|
||||
|
||||
/// Exchange two `result` values in an exception-safe manner.
|
||||
/** If the swap fails, the two values will be exactly as they were before.
|
||||
*
|
||||
* The swap is not necessarily thread-safe.
|
||||
*/
|
||||
void swap(result &) noexcept;
|
||||
|
||||
/// Index a row by number.
|
||||
/** This returns a @ref row object. Generally you should not keep the row
|
||||
* around as a variable, but if you do, make sure that your variable is a
|
||||
* `row`, not a `row&`.
|
||||
*/
|
||||
[[nodiscard]] row operator[](size_type i) const noexcept;
|
||||
|
||||
#if defined(PQXX_HAVE_MULTIDIMENSIONAL_SUBSCRIPT)
|
||||
// TODO: If C++23 will let us, also accept string for the column.
|
||||
[[nodiscard]] field
|
||||
operator[](size_type row_num, row_size_type col_num) const noexcept;
|
||||
#endif
|
||||
|
||||
/// Index a row by number, but check that the row number is valid.
|
||||
row at(size_type) const;
|
||||
|
||||
/// Index a field by row number and column number.
|
||||
field at(size_type, row_size_type) const;
|
||||
|
||||
/// Let go of the result's data.
|
||||
/** Use this if you need to deallocate the result data earlier than you can
|
||||
* destroy the `result` object itself.
|
||||
*
|
||||
* Multiple `result` objects can refer to the same set of underlying data.
|
||||
* The underlying data will be deallocated once all `result` objects that
|
||||
* refer to it are cleared or destroyed.
|
||||
*/
|
||||
void clear() noexcept
|
||||
{
|
||||
m_data.reset();
|
||||
m_query = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Column information
|
||||
*/
|
||||
//@{
|
||||
/// Number of columns in result.
|
||||
[[nodiscard]] PQXX_PURE row_size_type columns() const noexcept;
|
||||
|
||||
/// Number of given column (throws exception if it doesn't exist).
|
||||
[[nodiscard]] row_size_type column_number(zview name) const;
|
||||
|
||||
/// Name of column with this number (throws exception if it doesn't exist)
|
||||
[[nodiscard]] char const *column_name(row_size_type number) const &;
|
||||
|
||||
/// Return column's type, as an OID from the system catalogue.
|
||||
[[nodiscard]] oid column_type(row_size_type col_num) const;
|
||||
|
||||
/// Return column's type, as an OID from the system catalogue.
|
||||
[[nodiscard]] oid column_type(zview col_name) const
|
||||
{
|
||||
return column_type(column_number(col_name));
|
||||
}
|
||||
|
||||
/// What table did this column come from?
|
||||
[[nodiscard]] oid column_table(row_size_type col_num) const;
|
||||
|
||||
/// What table did this column come from?
|
||||
[[nodiscard]] oid column_table(zview col_name) const
|
||||
{
|
||||
return column_table(column_number(col_name));
|
||||
}
|
||||
|
||||
/// What column in its table did this column come from?
|
||||
[[nodiscard]] row_size_type table_column(row_size_type col_num) const;
|
||||
|
||||
/// What column in its table did this column come from?
|
||||
[[nodiscard]] row_size_type table_column(zview col_name) const
|
||||
{
|
||||
return table_column(column_number(col_name));
|
||||
}
|
||||
//@}
|
||||
|
||||
/// Query that produced this result, if available (empty string otherwise)
|
||||
[[nodiscard]] PQXX_PURE std::string const &query() const &noexcept;
|
||||
|
||||
/// If command was an `INSERT` of 1 row, return oid of the inserted row.
|
||||
/** @return Identifier of inserted row if exactly one row was inserted, or
|
||||
* @ref oid_none otherwise.
|
||||
*/
|
||||
[[nodiscard]] PQXX_PURE oid inserted_oid() const;
|
||||
|
||||
/// If command was `INSERT`, `UPDATE`, or `DELETE`: number of affected rows.
|
||||
/** @return Number of affected rows if last command was `INSERT`, `UPDATE`,
|
||||
* or `DELETE`; zero for all other commands.
|
||||
*/
|
||||
[[nodiscard]] PQXX_PURE size_type affected_rows() const;
|
||||
|
||||
// C++20: Concept like std::invocable, but without specifying param types.
|
||||
/// Run `func` on each row, passing the row's fields as parameters.
|
||||
/** Goes through the rows from first to last. You provide a callable `func`.
|
||||
*
|
||||
* For each row in the `result`, `for_each` will call `func`. It converts
|
||||
* the row's fields to the types of `func`'s parameters, and pass them to
|
||||
* `func`.
|
||||
*
|
||||
* (Therefore `func` must have a _single_ signature. It can't be a generic
|
||||
* lambda, or an object of a class with multiple overloaded function call
|
||||
* operators. Otherwise, `for_each` will have no way to detect a parameter
|
||||
* list without ambiguity.)
|
||||
*
|
||||
* If any of your parameter types is `std::string_view`, it refers to the
|
||||
* underlying storage of this `result`.
|
||||
*
|
||||
* If any of your parameter types is a reference type, its argument will
|
||||
* refer to a temporary value which only lives for the duration of that
|
||||
* single invocation to `func`. If the reference is an lvalue reference, it
|
||||
* must be `const`.
|
||||
*
|
||||
* For example, this queries employee names and salaries from the database
|
||||
* and prints how much each would like to earn instead:
|
||||
* ```cxx
|
||||
* tx.exec("SELECT name, salary FROM employee").for_each(
|
||||
* [](std::string_view name, float salary){
|
||||
* std::cout << name << " would like " << salary * 2 << ".\n";
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* If `func` throws an exception, processing stops at that point and
|
||||
* propagates the exception.
|
||||
*
|
||||
* @throws usage_error if `func`'s number of parameters does not match the
|
||||
* number of columns in this result.
|
||||
*/
|
||||
template<typename CALLABLE> inline void for_each(CALLABLE &&func) const;
|
||||
|
||||
private:
|
||||
using data_pointer = std::shared_ptr<internal::pq::PGresult const>;
|
||||
|
||||
/// Underlying libpq result set.
|
||||
data_pointer m_data;
|
||||
|
||||
/// Factory for data_pointer.
|
||||
static data_pointer
|
||||
make_data_pointer(internal::pq::PGresult const *res = nullptr) noexcept
|
||||
{
|
||||
return {res, internal::clear_result};
|
||||
}
|
||||
|
||||
friend class pqxx::internal::gate::result_pipeline;
|
||||
PQXX_PURE std::shared_ptr<std::string const> query_ptr() const noexcept
|
||||
{
|
||||
return m_query;
|
||||
}
|
||||
|
||||
/// Query string.
|
||||
std::shared_ptr<std::string const> m_query;
|
||||
|
||||
internal::encoding_group m_encoding;
|
||||
|
||||
static std::string const s_empty_string;
|
||||
|
||||
friend class pqxx::field;
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
PQXX_PURE char const *get_value(size_type row, row_size_type col) const;
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
PQXX_PURE bool get_is_null(size_type row, row_size_type col) const;
|
||||
PQXX_PURE
|
||||
field_size_type get_length(size_type, row_size_type) const noexcept;
|
||||
|
||||
friend class pqxx::internal::gate::result_creation;
|
||||
result(
|
||||
internal::pq::PGresult *rhs, std::shared_ptr<std::string> query,
|
||||
internal::encoding_group enc);
|
||||
|
||||
PQXX_PRIVATE void check_status(std::string_view desc = ""sv) const;
|
||||
|
||||
friend class pqxx::internal::gate::result_connection;
|
||||
friend class pqxx::internal::gate::result_row;
|
||||
bool operator!() const noexcept { return m_data.get() == nullptr; }
|
||||
operator bool() const noexcept { return m_data.get() != nullptr; }
|
||||
|
||||
[[noreturn]] PQXX_PRIVATE void
|
||||
throw_sql_error(std::string const &Err, std::string const &Query) const;
|
||||
PQXX_PRIVATE PQXX_PURE int errorposition() const;
|
||||
PQXX_PRIVATE std::string status_error() const;
|
||||
|
||||
friend class pqxx::internal::gate::result_sql_cursor;
|
||||
PQXX_PURE char const *cmd_status() const noexcept;
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::robusttransaction class.
|
||||
*
|
||||
* pqxx::robusttransaction is a slower but safer transaction class.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/robusttransaction.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,120 @@
|
||||
/* Definition of the pqxx::robusttransaction class.
|
||||
*
|
||||
* pqxx::robusttransaction is a slower but safer transaction class.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/robusttransaction 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_ROBUSTTRANSACTION
|
||||
#define PQXX_H_ROBUSTTRANSACTION
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/dbtransaction.hxx"
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
/// Helper base class for the @ref robusttransaction class template.
|
||||
class PQXX_LIBEXPORT PQXX_NOVTABLE basic_robusttransaction
|
||||
: public dbtransaction
|
||||
{
|
||||
public:
|
||||
virtual ~basic_robusttransaction() override = 0;
|
||||
|
||||
protected:
|
||||
basic_robusttransaction(
|
||||
connection &c, zview begin_command, std::string_view tname);
|
||||
basic_robusttransaction(connection &c, zview begin_command);
|
||||
|
||||
private:
|
||||
using IDType = unsigned long;
|
||||
|
||||
std::string m_conn_string;
|
||||
std::string m_xid;
|
||||
int m_backendpid = -1;
|
||||
|
||||
void init(zview begin_command);
|
||||
|
||||
// @warning This function will become `final`.
|
||||
virtual void do_commit() override;
|
||||
};
|
||||
} // namespace pqxx::internal
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/**
|
||||
* @ingroup transactions
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/// Slightly slower, better-fortified version of transaction.
|
||||
/** Requires PostgreSQL 10 or better.
|
||||
*
|
||||
* robusttransaction is similar to transaction, but spends more time and effort
|
||||
* to deal with the hopefully rare case that the connection to the backend is
|
||||
* lost just while it's trying to commit. In such cases, the client does not
|
||||
* know whether the backend (on the other side of the broken connection)
|
||||
* managed to commit the transaction.
|
||||
*
|
||||
* When this happens, robusttransaction tries to reconnect to the database and
|
||||
* figure out what happened.
|
||||
*
|
||||
* This service level was made optional since you may not want to pay the
|
||||
* overhead where it is not necessary. Certainly the use of this class makes
|
||||
* no sense for local connections, or for transactions that read the database
|
||||
* but never modify it, or for noncritical database manipulations.
|
||||
*
|
||||
* Besides being slower, it's also more complex. Which means that in practice
|
||||
* a robusttransaction could actually fail more instead of less often than a
|
||||
* normal transaction. What robusttransaction tries to achieve is to give you
|
||||
* certainty, not just be more successful per se.
|
||||
*/
|
||||
template<isolation_level ISOLATION = read_committed>
|
||||
class robusttransaction final : public internal::basic_robusttransaction
|
||||
{
|
||||
public:
|
||||
/** Create robusttransaction of given name.
|
||||
* @param c Connection inside which this robusttransaction should live.
|
||||
* @param tname optional human-readable name for this transaction.
|
||||
*/
|
||||
robusttransaction(connection &c, std::string_view tname) :
|
||||
internal::basic_robusttransaction{
|
||||
c, pqxx::internal::begin_cmd<ISOLATION, write_policy::read_write>,
|
||||
tname}
|
||||
{}
|
||||
|
||||
/** Create robusttransaction of given name.
|
||||
* @param c Connection inside which this robusttransaction should live.
|
||||
* @param tname optional human-readable name for this transaction.
|
||||
*/
|
||||
robusttransaction(connection &c, std::string &&tname) :
|
||||
internal::basic_robusttransaction{
|
||||
c, pqxx::internal::begin_cmd<ISOLATION, write_policy::read_write>,
|
||||
std::move(tname)}
|
||||
{}
|
||||
|
||||
/** Create robusttransaction of given name.
|
||||
* @param c Connection inside which this robusttransaction should live.
|
||||
*/
|
||||
explicit robusttransaction(connection &c) :
|
||||
internal::basic_robusttransaction{
|
||||
c, pqxx::internal::begin_cmd<ISOLATION, write_policy::read_write>}
|
||||
{}
|
||||
|
||||
virtual ~robusttransaction() noexcept override { close(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
11
ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row
Normal file
11
ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row
Normal file
@@ -0,0 +1,11 @@
|
||||
/** pqxx::row class.
|
||||
*
|
||||
* pqxx::row refers to a row in a result.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
|
||||
#include "pqxx/result.hxx"
|
||||
#include "pqxx/row.hxx"
|
||||
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
561
ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row.hxx
Normal file
561
ext/libpqxx-7.7.3/install/ubuntu22.04/amd64/include/pqxx/row.hxx
Normal file
@@ -0,0 +1,561 @@
|
||||
/* Definitions for the pqxx::result class and support classes.
|
||||
*
|
||||
* pqxx::result represents the set of result rows from a database query.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/result 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_ROW
|
||||
#define PQXX_H_ROW
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/except.hxx"
|
||||
#include "pqxx/field.hxx"
|
||||
#include "pqxx/result.hxx"
|
||||
|
||||
#include "pqxx/internal/concat.hxx"
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
template<typename... T> class result_iter;
|
||||
} // namespace pqxx::internal
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Reference to one row in a result.
|
||||
/** A row represents one row (also called a row) in a query result set.
|
||||
* It also acts as a container mapping column numbers or names to field
|
||||
* values (see below):
|
||||
*
|
||||
* ```cxx
|
||||
* cout << row["date"].c_str() << ": " << row["name"].c_str() << endl;
|
||||
* ```
|
||||
*
|
||||
* The row itself acts like a (non-modifyable) container, complete with its
|
||||
* own const_iterator and const_reverse_iterator.
|
||||
*/
|
||||
class PQXX_LIBEXPORT row
|
||||
{
|
||||
public:
|
||||
using size_type = row_size_type;
|
||||
using difference_type = row_difference_type;
|
||||
using const_iterator = const_row_iterator;
|
||||
using iterator = const_iterator;
|
||||
using reference = field;
|
||||
using pointer = const_row_iterator;
|
||||
using const_reverse_iterator = const_reverse_row_iterator;
|
||||
using reverse_iterator = const_reverse_iterator;
|
||||
|
||||
row() noexcept = default;
|
||||
row(row &&) noexcept = default;
|
||||
row(row const &) noexcept = default;
|
||||
row &operator=(row const &) noexcept = default;
|
||||
row &operator=(row &&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @name Comparison
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] PQXX_PURE bool operator==(row const &) const noexcept;
|
||||
[[nodiscard]] bool operator!=(row const &rhs) const noexcept
|
||||
{
|
||||
return not operator==(rhs);
|
||||
}
|
||||
//@}
|
||||
|
||||
[[nodiscard]] const_iterator begin() const noexcept;
|
||||
[[nodiscard]] const_iterator cbegin() const noexcept;
|
||||
[[nodiscard]] const_iterator end() const noexcept;
|
||||
[[nodiscard]] const_iterator cend() const noexcept;
|
||||
|
||||
/**
|
||||
* @name Field access
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] reference front() const noexcept;
|
||||
[[nodiscard]] reference back() const noexcept;
|
||||
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
[[nodiscard]] const_reverse_row_iterator rbegin() const;
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
[[nodiscard]] const_reverse_row_iterator crbegin() const;
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
[[nodiscard]] const_reverse_row_iterator rend() const;
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
[[nodiscard]] const_reverse_row_iterator crend() const;
|
||||
|
||||
[[nodiscard]] reference operator[](size_type) const noexcept;
|
||||
/** Address field by name.
|
||||
* @warning This is much slower than indexing by number, or iterating.
|
||||
*/
|
||||
[[nodiscard]] reference operator[](zview col_name) const;
|
||||
|
||||
reference at(size_type) const;
|
||||
/** Address field by name.
|
||||
* @warning This is much slower than indexing by number, or iterating.
|
||||
*/
|
||||
reference at(zview col_name) const;
|
||||
|
||||
[[nodiscard]] constexpr size_type size() const noexcept
|
||||
{
|
||||
return m_end - m_begin;
|
||||
}
|
||||
|
||||
[[deprecated("Swap iterators, not rows.")]] void swap(row &) noexcept;
|
||||
|
||||
/// Row number, assuming this is a real row and not end()/rend().
|
||||
[[nodiscard]] constexpr result::size_type rownumber() const noexcept
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Column information
|
||||
*/
|
||||
//@{
|
||||
/// Number of given column (throws exception if it doesn't exist).
|
||||
[[nodiscard]] size_type column_number(zview col_name) const;
|
||||
|
||||
/// Return a column's type.
|
||||
[[nodiscard]] oid column_type(size_type) const;
|
||||
|
||||
/// Return a column's type.
|
||||
[[nodiscard]] oid column_type(zview col_name) const
|
||||
{
|
||||
return column_type(column_number(col_name));
|
||||
}
|
||||
|
||||
/// What table did this column come from?
|
||||
[[nodiscard]] oid column_table(size_type col_num) const;
|
||||
|
||||
/// What table did this column come from?
|
||||
[[nodiscard]] oid column_table(zview col_name) const
|
||||
{
|
||||
return column_table(column_number(col_name));
|
||||
}
|
||||
|
||||
/// What column number in its table did this result column come from?
|
||||
/** A meaningful answer can be given only if the column in question comes
|
||||
* directly from a column in a table. If the column is computed in any
|
||||
* other way, a logic_error will be thrown.
|
||||
*
|
||||
* @param col_num a zero-based column number in this result set
|
||||
* @return a zero-based column number in originating table
|
||||
*/
|
||||
[[nodiscard]] size_type table_column(size_type) const;
|
||||
|
||||
/// What column number in its table did this result column come from?
|
||||
[[nodiscard]] size_type table_column(zview col_name) const
|
||||
{
|
||||
return table_column(column_number(col_name));
|
||||
}
|
||||
//@}
|
||||
|
||||
[[nodiscard]] constexpr result::size_type num() const noexcept
|
||||
{
|
||||
return rownumber();
|
||||
}
|
||||
|
||||
/** Produce a slice of this row, containing the given range of columns.
|
||||
*
|
||||
* @deprecated I haven't heard of anyone caring about row slicing at all in
|
||||
* at least the last 15 years. Yet it adds complexity, so unless anyone
|
||||
* files a bug explaining why they really need this feature, I'm going to
|
||||
* remove it. Even if they do, the feature may need an update.
|
||||
*
|
||||
* The slice runs from the range's starting column to the range's end
|
||||
* column, exclusive. It looks just like a normal result row, except
|
||||
* slices can be empty.
|
||||
*/
|
||||
[[deprecated("Row slicing is going away. File a bug if you need it.")]] row
|
||||
slice(size_type sbegin, size_type send) const;
|
||||
|
||||
/// Is this a row without fields? Can only happen to a slice.
|
||||
[[nodiscard, deprecated("Row slicing is going away.")]] PQXX_PURE bool
|
||||
empty() const noexcept;
|
||||
|
||||
/// Extract entire row's values into a tuple.
|
||||
/** Converts to the types of the tuple's respective fields.
|
||||
*/
|
||||
template<typename Tuple> void to(Tuple &t) const
|
||||
{
|
||||
check_size(std::tuple_size_v<Tuple>);
|
||||
convert(t);
|
||||
}
|
||||
|
||||
template<typename... TYPE> std::tuple<TYPE...> as() const
|
||||
{
|
||||
check_size(sizeof...(TYPE));
|
||||
using seq = std::make_index_sequence<sizeof...(TYPE)>;
|
||||
return get_tuple<std::tuple<TYPE...>>(seq{});
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class const_row_iterator;
|
||||
friend class result;
|
||||
row(result const &r, result_size_type index, size_type cols) noexcept;
|
||||
|
||||
/// Throw @ref usage_error if row size is not `expected`.
|
||||
void check_size(size_type expected) const
|
||||
{
|
||||
if (size() != expected)
|
||||
throw usage_error{internal::concat(
|
||||
"Tried to extract ", expected, " field(s) from a row of ", size(),
|
||||
".")};
|
||||
}
|
||||
|
||||
/// Convert to a given tuple of values, don't check sizes.
|
||||
/** We need this for cases where we have a full tuple of field types, but
|
||||
* not a parameter pack.
|
||||
*/
|
||||
template<typename TUPLE> TUPLE as_tuple() const
|
||||
{
|
||||
using seq = std::make_index_sequence<std::tuple_size_v<TUPLE>>;
|
||||
return get_tuple<TUPLE>(seq{});
|
||||
}
|
||||
|
||||
template<typename... T> friend class pqxx::internal::result_iter;
|
||||
/// Convert entire row to tuple fields, without checking row size.
|
||||
template<typename Tuple> void convert(Tuple &t) const
|
||||
{
|
||||
extract_fields(t, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
|
||||
}
|
||||
|
||||
friend class field;
|
||||
|
||||
/// Result set of which this is one row.
|
||||
result m_result;
|
||||
|
||||
/// Row number.
|
||||
/**
|
||||
* You'd expect this to be unsigned, but due to the way reverse iterators
|
||||
* are related to regular iterators, it must be allowed to underflow to -1.
|
||||
*/
|
||||
result::size_type m_index = 0;
|
||||
|
||||
// TODO: Remove m_begin and (if possible) m_end when we remove slice().
|
||||
/// First column in slice. This row ignores lower-numbered columns.
|
||||
size_type m_begin = 0;
|
||||
/// End column in slice. This row only sees lower-numbered columns.
|
||||
size_type m_end = 0;
|
||||
|
||||
private:
|
||||
template<typename Tuple, std::size_t... indexes>
|
||||
void extract_fields(Tuple &t, std::index_sequence<indexes...>) const
|
||||
{
|
||||
(extract_value<Tuple, indexes>(t), ...);
|
||||
}
|
||||
|
||||
template<typename Tuple, std::size_t index>
|
||||
void extract_value(Tuple &t) const;
|
||||
|
||||
/// Convert row's values as a new tuple.
|
||||
template<typename TUPLE, std::size_t... indexes>
|
||||
auto get_tuple(std::index_sequence<indexes...>) const
|
||||
{
|
||||
return std::make_tuple(get_field<TUPLE, indexes>()...);
|
||||
}
|
||||
|
||||
/// Extract and convert a field.
|
||||
template<typename TUPLE, std::size_t index> auto get_field() const
|
||||
{
|
||||
return (*this)[index].as<std::tuple_element_t<index, TUPLE>>();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// Iterator for fields in a row. Use as row::const_iterator.
|
||||
class PQXX_LIBEXPORT const_row_iterator : public field
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
using value_type = field const;
|
||||
using pointer = field const *;
|
||||
using size_type = row_size_type;
|
||||
using difference_type = row_difference_type;
|
||||
using reference = field;
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
const_row_iterator() = default;
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
const_row_iterator(row const &t, row_size_type c) noexcept :
|
||||
field{t.m_result, t.m_index, c}
|
||||
{}
|
||||
const_row_iterator(field const &F) noexcept : field{F} {}
|
||||
const_row_iterator(const_row_iterator const &) noexcept = default;
|
||||
const_row_iterator(const_row_iterator &&) noexcept = default;
|
||||
|
||||
/**
|
||||
* @name Dereferencing operators
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] constexpr pointer operator->() const noexcept { return this; }
|
||||
[[nodiscard]] reference operator*() const noexcept { return {*this}; }
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Manipulations
|
||||
*/
|
||||
//@{
|
||||
const_row_iterator &operator=(const_row_iterator const &) noexcept = default;
|
||||
const_row_iterator &operator=(const_row_iterator &&) noexcept = default;
|
||||
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
const_row_iterator operator++(int);
|
||||
const_row_iterator &operator++() noexcept
|
||||
{
|
||||
++m_col;
|
||||
return *this;
|
||||
}
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
const_row_iterator operator--(int);
|
||||
const_row_iterator &operator--() noexcept
|
||||
{
|
||||
--m_col;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_row_iterator &operator+=(difference_type i) noexcept
|
||||
{
|
||||
m_col = size_type(difference_type(m_col) + i);
|
||||
return *this;
|
||||
}
|
||||
const_row_iterator &operator-=(difference_type i) noexcept
|
||||
{
|
||||
m_col = size_type(difference_type(m_col) - i);
|
||||
return *this;
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Comparisons
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] constexpr bool
|
||||
operator==(const_row_iterator const &i) const noexcept
|
||||
{
|
||||
return col() == i.col();
|
||||
}
|
||||
[[nodiscard]] constexpr bool
|
||||
operator!=(const_row_iterator const &i) const noexcept
|
||||
{
|
||||
return col() != i.col();
|
||||
}
|
||||
[[nodiscard]] constexpr bool
|
||||
operator<(const_row_iterator const &i) const noexcept
|
||||
{
|
||||
return col() < i.col();
|
||||
}
|
||||
[[nodiscard]] constexpr bool
|
||||
operator<=(const_row_iterator const &i) const noexcept
|
||||
{
|
||||
return col() <= i.col();
|
||||
}
|
||||
[[nodiscard]] constexpr bool
|
||||
operator>(const_row_iterator const &i) const noexcept
|
||||
{
|
||||
return col() > i.col();
|
||||
}
|
||||
[[nodiscard]] constexpr bool
|
||||
operator>=(const_row_iterator const &i) const noexcept
|
||||
{
|
||||
return col() >= i.col();
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Arithmetic operators
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] inline const_row_iterator
|
||||
operator+(difference_type) const noexcept;
|
||||
|
||||
friend const_row_iterator
|
||||
operator+(difference_type, const_row_iterator const &) noexcept;
|
||||
|
||||
[[nodiscard]] inline const_row_iterator
|
||||
operator-(difference_type) const noexcept;
|
||||
[[nodiscard]] inline difference_type
|
||||
operator-(const_row_iterator const &) const noexcept;
|
||||
//@}
|
||||
};
|
||||
|
||||
|
||||
/// Reverse iterator for a row. Use as row::const_reverse_iterator.
|
||||
class PQXX_LIBEXPORT const_reverse_row_iterator : private const_row_iterator
|
||||
{
|
||||
public:
|
||||
using super = const_row_iterator;
|
||||
using iterator_type = const_row_iterator;
|
||||
using iterator_type::difference_type;
|
||||
using iterator_type::iterator_category;
|
||||
using iterator_type::pointer;
|
||||
using value_type = iterator_type::value_type;
|
||||
using reference = iterator_type::reference;
|
||||
|
||||
const_reverse_row_iterator() noexcept = default;
|
||||
const_reverse_row_iterator(const_reverse_row_iterator const &) noexcept =
|
||||
default;
|
||||
const_reverse_row_iterator(const_reverse_row_iterator &&) noexcept = default;
|
||||
|
||||
explicit const_reverse_row_iterator(super const &rhs) noexcept :
|
||||
const_row_iterator{rhs}
|
||||
{
|
||||
super::operator--();
|
||||
}
|
||||
|
||||
[[nodiscard]] PQXX_PURE iterator_type base() const noexcept;
|
||||
|
||||
/**
|
||||
* @name Dereferencing operators
|
||||
*/
|
||||
//@{
|
||||
using iterator_type::operator->;
|
||||
using iterator_type::operator*;
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Manipulations
|
||||
*/
|
||||
//@{
|
||||
const_reverse_row_iterator &
|
||||
operator=(const_reverse_row_iterator const &r) noexcept
|
||||
{
|
||||
iterator_type::operator=(r);
|
||||
return *this;
|
||||
}
|
||||
const_reverse_row_iterator operator++() noexcept
|
||||
{
|
||||
iterator_type::operator--();
|
||||
return *this;
|
||||
}
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
const_reverse_row_iterator operator++(int);
|
||||
const_reverse_row_iterator &operator--() noexcept
|
||||
{
|
||||
iterator_type::operator++();
|
||||
return *this;
|
||||
}
|
||||
const_reverse_row_iterator operator--(int);
|
||||
// TODO: noexcept. Breaks ABI.
|
||||
const_reverse_row_iterator &operator+=(difference_type i) noexcept
|
||||
{
|
||||
iterator_type::operator-=(i);
|
||||
return *this;
|
||||
}
|
||||
const_reverse_row_iterator &operator-=(difference_type i) noexcept
|
||||
{
|
||||
iterator_type::operator+=(i);
|
||||
return *this;
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Arithmetic operators
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] const_reverse_row_iterator
|
||||
operator+(difference_type i) const noexcept
|
||||
{
|
||||
return const_reverse_row_iterator{base() - i};
|
||||
}
|
||||
[[nodiscard]] const_reverse_row_iterator
|
||||
operator-(difference_type i) noexcept
|
||||
{
|
||||
return const_reverse_row_iterator{base() + i};
|
||||
}
|
||||
[[nodiscard]] difference_type
|
||||
operator-(const_reverse_row_iterator const &rhs) const noexcept
|
||||
{
|
||||
return rhs.const_row_iterator::operator-(*this);
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Comparisons
|
||||
*/
|
||||
//@{
|
||||
[[nodiscard]] bool
|
||||
operator==(const_reverse_row_iterator const &rhs) const noexcept
|
||||
{
|
||||
return iterator_type::operator==(rhs);
|
||||
}
|
||||
[[nodiscard]] bool
|
||||
operator!=(const_reverse_row_iterator const &rhs) const noexcept
|
||||
{
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool
|
||||
operator<(const_reverse_row_iterator const &rhs) const noexcept
|
||||
{
|
||||
return iterator_type::operator>(rhs);
|
||||
}
|
||||
[[nodiscard]] constexpr bool
|
||||
operator<=(const_reverse_row_iterator const &rhs) const noexcept
|
||||
{
|
||||
return iterator_type::operator>=(rhs);
|
||||
}
|
||||
[[nodiscard]] constexpr bool
|
||||
operator>(const_reverse_row_iterator const &rhs) const noexcept
|
||||
{
|
||||
return iterator_type::operator<(rhs);
|
||||
}
|
||||
[[nodiscard]] constexpr bool
|
||||
operator>=(const_reverse_row_iterator const &rhs) const noexcept
|
||||
{
|
||||
return iterator_type::operator<=(rhs);
|
||||
}
|
||||
//@}
|
||||
};
|
||||
|
||||
|
||||
const_row_iterator
|
||||
const_row_iterator::operator+(difference_type o) const noexcept
|
||||
{
|
||||
// TODO:: More direct route to home().columns()?
|
||||
return {
|
||||
row{home(), idx(), home().columns()},
|
||||
size_type(difference_type(col()) + o)};
|
||||
}
|
||||
|
||||
inline const_row_iterator operator+(
|
||||
const_row_iterator::difference_type o, const_row_iterator const &i) noexcept
|
||||
{
|
||||
return i + o;
|
||||
}
|
||||
|
||||
inline const_row_iterator
|
||||
const_row_iterator::operator-(difference_type o) const noexcept
|
||||
{
|
||||
// TODO:: More direct route to home().columns()?
|
||||
return {
|
||||
row{home(), idx(), home().columns()},
|
||||
size_type(difference_type(col()) - o)};
|
||||
}
|
||||
|
||||
inline const_row_iterator::difference_type
|
||||
const_row_iterator::operator-(const_row_iterator const &i) const noexcept
|
||||
{
|
||||
return difference_type(num() - i.num());
|
||||
}
|
||||
|
||||
|
||||
template<typename Tuple, std::size_t index>
|
||||
inline void row::extract_value(Tuple &t) const
|
||||
{
|
||||
using field_type = strip_t<decltype(std::get<index>(t))>;
|
||||
field const f{m_result, m_index, index};
|
||||
std::get<index>(t) = from_string<field_type>(f);
|
||||
}
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
/** Helper similar to Python's @c str.join().
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/separated_list.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,142 @@
|
||||
/* Helper similar to Python's `str.join()`.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/separated_list 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_SEPARATED_LIST
|
||||
#define PQXX_H_SEPARATED_LIST
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "pqxx/strconv.hxx"
|
||||
|
||||
// C++20: Simplify using std::ranges::range.
|
||||
// C++20: Optimise buffer allocation using random_access_range/iterator.
|
||||
namespace pqxx
|
||||
{
|
||||
/**
|
||||
* @defgroup utility Utility functions
|
||||
*/
|
||||
//@{
|
||||
|
||||
/// Represent sequence of values as a string, joined by a given separator.
|
||||
/**
|
||||
* Use this to turn e.g. the numbers 1, 2, and 3 into a string "1, 2, 3".
|
||||
*
|
||||
* @param sep separator string (to be placed between items)
|
||||
* @param begin beginning of items sequence
|
||||
* @param end end of items sequence
|
||||
* @param access functor defining how to dereference sequence elements
|
||||
*/
|
||||
template<typename ITER, typename ACCESS>
|
||||
[[nodiscard]] inline std::string
|
||||
separated_list(std::string_view sep, ITER begin, ITER end, ACCESS access)
|
||||
{
|
||||
if (end == begin)
|
||||
return {};
|
||||
auto next{begin};
|
||||
++next;
|
||||
if (next == end)
|
||||
return to_string(access(begin));
|
||||
|
||||
// From here on, we've got at least 2 elements -- meaning that we need sep.
|
||||
using elt_type = strip_t<decltype(access(begin))>;
|
||||
using traits = string_traits<elt_type>;
|
||||
|
||||
std::size_t budget{0};
|
||||
for (ITER cnt{begin}; cnt != end; ++cnt)
|
||||
budget += traits::size_buffer(access(cnt));
|
||||
budget +=
|
||||
static_cast<std::size_t>(std::distance(begin, end)) * std::size(sep);
|
||||
|
||||
std::string result;
|
||||
result.resize(budget);
|
||||
|
||||
char *const data{result.data()};
|
||||
char *here{data};
|
||||
char *stop{data + budget};
|
||||
here = traits::into_buf(here, stop, access(begin)) - 1;
|
||||
for (++begin; begin != end; ++begin)
|
||||
{
|
||||
here += sep.copy(here, std::size(sep));
|
||||
here = traits::into_buf(here, stop, access(begin)) - 1;
|
||||
}
|
||||
result.resize(static_cast<std::size_t>(here - data));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// Render sequence as a string, using given separator between items.
|
||||
template<typename ITER>
|
||||
[[nodiscard]] inline std::string
|
||||
separated_list(std::string_view sep, ITER begin, ITER end)
|
||||
{
|
||||
return separated_list(sep, begin, end, [](ITER i) { return *i; });
|
||||
}
|
||||
|
||||
|
||||
/// Render items in a container as a string, using given separator.
|
||||
template<typename CONTAINER>
|
||||
[[nodiscard]] inline auto
|
||||
separated_list(std::string_view sep, CONTAINER const &c)
|
||||
/*
|
||||
Always std::string; necessary because SFINAE doesn't work with the
|
||||
contents of function bodies, so the check for iterability has to be in
|
||||
the signature.
|
||||
*/
|
||||
-> typename std::enable_if<
|
||||
(not std::is_void<decltype(std::begin(c))>::value and
|
||||
not std::is_void<decltype(std::end(c))>::value),
|
||||
std::string>::type
|
||||
{
|
||||
return separated_list(sep, std::begin(c), std::end(c));
|
||||
}
|
||||
|
||||
|
||||
/// Render items in a tuple as a string, using given separator.
|
||||
template<
|
||||
typename TUPLE, std::size_t INDEX = 0, typename ACCESS,
|
||||
typename std::enable_if<
|
||||
(INDEX == std::tuple_size<TUPLE>::value - 1), int>::type = 0>
|
||||
[[nodiscard]] inline std::string separated_list(
|
||||
std::string_view /* sep */, TUPLE const &t, ACCESS const &access)
|
||||
{
|
||||
return to_string(access(&std::get<INDEX>(t)));
|
||||
}
|
||||
|
||||
template<
|
||||
typename TUPLE, std::size_t INDEX = 0, typename ACCESS,
|
||||
typename std::enable_if<
|
||||
(INDEX < std::tuple_size<TUPLE>::value - 1), int>::type = 0>
|
||||
[[nodiscard]] inline std::string
|
||||
separated_list(std::string_view sep, TUPLE const &t, ACCESS const &access)
|
||||
{
|
||||
std::string out{to_string(access(&std::get<INDEX>(t)))};
|
||||
out.append(sep);
|
||||
out.append(separated_list<TUPLE, INDEX + 1>(sep, t, access));
|
||||
return out;
|
||||
}
|
||||
|
||||
template<
|
||||
typename TUPLE, std::size_t INDEX = 0,
|
||||
typename std::enable_if<
|
||||
(INDEX <= std::tuple_size<TUPLE>::value), int>::type = 0>
|
||||
[[nodiscard]] inline std::string
|
||||
separated_list(std::string_view sep, TUPLE const &t)
|
||||
{
|
||||
// TODO: Optimise allocation.
|
||||
return separated_list(sep, t, [](TUPLE const &tup) { return *tup; });
|
||||
}
|
||||
//@}
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
/** String conversion definitions.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/strconv.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,468 @@
|
||||
/* String conversion definitions.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stringconv 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_STRCONV
|
||||
#define PQXX_H_STRCONV
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
|
||||
#if __has_include(<charconv>)
|
||||
# include <charconv>
|
||||
#endif
|
||||
|
||||
#if defined(PQXX_HAVE_RANGES) && __has_include(<ranges>)
|
||||
# include <ranges>
|
||||
#endif
|
||||
|
||||
#include "pqxx/except.hxx"
|
||||
#include "pqxx/util.hxx"
|
||||
#include "pqxx/zview.hxx"
|
||||
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
/// Attempt to demangle @c std::type_info::name() to something human-readable.
|
||||
PQXX_LIBEXPORT std::string demangle_type_name(char const[]);
|
||||
} // namespace pqxx::internal
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/**
|
||||
* @defgroup stringconversion String conversion
|
||||
*
|
||||
* The PostgreSQL server accepts and represents data in string form. It has
|
||||
* its own formats for various data types. The string conversions define how
|
||||
* various C++ types translate to and from their respective PostgreSQL text
|
||||
* representations.
|
||||
*
|
||||
* Each conversion is defined by a specialisations of @c string_traits. It
|
||||
* gets complicated if you want top performance, but until you do, all you
|
||||
* really need to care about when converting values between C++ in-memory
|
||||
* representations such as @c int and the postgres string representations is
|
||||
* the @c pqxx::to_string and @c pqxx::from_string functions.
|
||||
*
|
||||
* If you need to convert a type which is not supported out of the box, you'll
|
||||
* need to define your own specialisations for these templates, similar to the
|
||||
* ones defined here and in `pqxx/conversions.hxx`. Any conversion code which
|
||||
* "sees" your specialisation will now support your conversion. In particular,
|
||||
* you'll be able to read result fields into a variable of the new type.
|
||||
*
|
||||
* There is a macro to help you define conversions for individual enumeration
|
||||
* types. The conversion will represent enumeration values as numeric strings.
|
||||
*/
|
||||
//@{
|
||||
|
||||
/// A human-readable name for a type, used in error messages and such.
|
||||
/** Actually this may not always be very user-friendly. It uses
|
||||
* @c std::type_info::name(). On gcc-like compilers we try to demangle its
|
||||
* output. Visual Studio produces human-friendly names out of the box.
|
||||
*
|
||||
* This variable is not inline. Inlining it gives rise to "memory leak"
|
||||
* warnings from asan, the address sanitizer, possibly from use of
|
||||
* @c std::type_info::name.
|
||||
*/
|
||||
template<typename TYPE>
|
||||
std::string const type_name{internal::demangle_type_name(typeid(TYPE).name())};
|
||||
|
||||
|
||||
/// Traits describing a type's "null value," if any.
|
||||
/** Some C++ types have a special value or state which correspond directly to
|
||||
* SQL's NULL.
|
||||
*
|
||||
* The @c nullness traits describe whether it exists, and whether a particular
|
||||
* value is null.
|
||||
*/
|
||||
template<typename TYPE, typename ENABLE = void> struct nullness
|
||||
{
|
||||
/// Does this type have a null value?
|
||||
static bool has_null;
|
||||
|
||||
/// Is this type always null?
|
||||
static bool always_null;
|
||||
|
||||
/// Is @c value a null?
|
||||
static bool is_null(TYPE const &value);
|
||||
|
||||
/// Return a null value.
|
||||
/** Don't use this in generic code to compare a value and see whether it is
|
||||
* null. Some types may have multiple null values which do not compare as
|
||||
* equal, or may define a null value which is not equal to anything including
|
||||
* itself, like in SQL.
|
||||
*/
|
||||
[[nodiscard]] static TYPE null();
|
||||
};
|
||||
|
||||
|
||||
/// Nullness traits describing a type which does not have a null value.
|
||||
template<typename TYPE> struct no_null
|
||||
{
|
||||
/// Does @c TYPE have a "built-in null value"?
|
||||
/** For example, a pointer can equal @c nullptr, which makes a very natural
|
||||
* representation of an SQL null value. For such types, the code sometimes
|
||||
* needs to make special allowances.
|
||||
*
|
||||
* for most types, such as @c int or @c std::string, there is no built-in
|
||||
* null. If you want to represent an SQL null value for such a type, you
|
||||
* would have to wrap it in something that does have a null value. For
|
||||
* example, you could use @c std::optional<int> for "either an @c int or a
|
||||
* null value."
|
||||
*/
|
||||
static constexpr bool has_null = false;
|
||||
|
||||
/// Are all values of this type null?
|
||||
/** There are a few special C++ types which are always null - mainly
|
||||
* @c std::nullptr_t.
|
||||
*/
|
||||
static constexpr bool always_null = false;
|
||||
|
||||
/// Does a given value correspond to an SQL null value?
|
||||
/** Most C++ types, such as @c int or @c std::string, have no inherent null
|
||||
* value. But some types such as C-style string pointers do have a natural
|
||||
* equivalent to an SQL null.
|
||||
*/
|
||||
[[nodiscard]] static constexpr bool is_null(TYPE const &) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// Traits class for use in string conversions.
|
||||
/** Specialize this template for a type for which you wish to add to_string
|
||||
* and from_string support.
|
||||
*
|
||||
* String conversions are not meant to work for nulls. Check for null before
|
||||
* converting a value of @c TYPE to a string, or vice versa.
|
||||
*/
|
||||
template<typename TYPE> struct string_traits
|
||||
{
|
||||
/// Return a @c string_view representing value, plus terminating zero.
|
||||
/** Produces a @c string_view containing the PostgreSQL string representation
|
||||
* for @c value.
|
||||
*
|
||||
* Uses the space from @c begin to @c end as a buffer, if needed. The
|
||||
* returned string may lie somewhere in that buffer, or it may be a
|
||||
* compile-time constant, or it may be null if value was a null value. Even
|
||||
* if the string is stored in the buffer, its @c begin() may or may not be
|
||||
* the same as @c begin.
|
||||
*
|
||||
* The @c string_view is guaranteed to be valid as long as the buffer from
|
||||
* @c begin to @c end remains accessible and unmodified.
|
||||
*
|
||||
* @throws pqxx::conversion_overrun if the provided buffer space may not be
|
||||
* enough. For maximum performance, this is a conservative estimate. It may
|
||||
* complain about a buffer which is actually large enough for your value, if
|
||||
* an exact check gets too expensive.
|
||||
*/
|
||||
[[nodiscard]] static inline zview
|
||||
to_buf(char *begin, char *end, TYPE const &value);
|
||||
|
||||
/// Write value's string representation into buffer at @c begin.
|
||||
/** Assumes that value is non-null.
|
||||
*
|
||||
* Writes value's string representation into the buffer, starting exactly at
|
||||
* @c begin, and ensuring a trailing zero. Returns the address just beyond
|
||||
* the trailing zero, so the caller could use it as the @c begin for another
|
||||
* call to @c into_buf writing a next value.
|
||||
*/
|
||||
static inline char *into_buf(char *begin, char *end, TYPE const &value);
|
||||
|
||||
/// Parse a string representation of a @c TYPE value.
|
||||
/** Throws @c conversion_error if @c value does not meet the expected format
|
||||
* for a value of this type.
|
||||
*/
|
||||
[[nodiscard]] static inline TYPE from_string(std::string_view text);
|
||||
|
||||
// C++20: Can we make these all constexpr?
|
||||
/// Estimate how much buffer space is needed to represent value.
|
||||
/** The estimate may be a little pessimistic, if it saves time.
|
||||
*
|
||||
* The estimate includes the terminating zero.
|
||||
*/
|
||||
[[nodiscard]] static inline std::size_t
|
||||
size_buffer(TYPE const &value) noexcept;
|
||||
};
|
||||
|
||||
|
||||
/// Nullness: Enums do not have an inherent null value.
|
||||
template<typename ENUM>
|
||||
struct nullness<ENUM, std::enable_if_t<std::is_enum_v<ENUM>>> : no_null<ENUM>
|
||||
{};
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
/// Helper class for defining enum conversions.
|
||||
/** The conversion will convert enum values to numeric strings, and vice versa.
|
||||
*
|
||||
* To define a string conversion for an enum type, derive a @c string_traits
|
||||
* specialisation for the enum from this struct.
|
||||
*
|
||||
* There's usually an easier way though: the @c PQXX_DECLARE_ENUM_CONVERSION
|
||||
* macro. Use @c enum_traits manually only if you need to customise your
|
||||
* traits type in more detail.
|
||||
*/
|
||||
template<typename ENUM> struct enum_traits
|
||||
{
|
||||
using impl_type = std::underlying_type_t<ENUM>;
|
||||
using impl_traits = string_traits<impl_type>;
|
||||
|
||||
[[nodiscard]] static constexpr zview
|
||||
to_buf(char *begin, char *end, ENUM const &value)
|
||||
{
|
||||
return impl_traits::to_buf(begin, end, to_underlying(value));
|
||||
}
|
||||
|
||||
static constexpr char *into_buf(char *begin, char *end, ENUM const &value)
|
||||
{
|
||||
return impl_traits::into_buf(begin, end, to_underlying(value));
|
||||
}
|
||||
|
||||
[[nodiscard]] static ENUM from_string(std::string_view text)
|
||||
{
|
||||
return static_cast<ENUM>(impl_traits::from_string(text));
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::size_t size_buffer(ENUM const &value) noexcept
|
||||
{
|
||||
return impl_traits::size_buffer(to_underlying(value));
|
||||
}
|
||||
|
||||
private:
|
||||
// C++23: Replace with std::to_underlying.
|
||||
static constexpr impl_type to_underlying(ENUM const &value) noexcept
|
||||
{
|
||||
return static_cast<impl_type>(value);
|
||||
}
|
||||
};
|
||||
} // namespace pqxx::internal
|
||||
|
||||
|
||||
/// Macro: Define a string conversion for an enum type.
|
||||
/** This specialises the @c pqxx::string_traits template, so use it in the
|
||||
* @c ::pqxx namespace.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* #include <iostream>
|
||||
* #include <pqxx/strconv>
|
||||
* enum X { xa, xb };
|
||||
* namespace pqxx { PQXX_DECLARE_ENUM_CONVERSION(x); }
|
||||
* int main() { std::cout << pqxx::to_string(xa) << std::endl; }
|
||||
*/
|
||||
#define PQXX_DECLARE_ENUM_CONVERSION(ENUM) \
|
||||
template<> struct string_traits<ENUM> : pqxx::internal::enum_traits<ENUM> \
|
||||
{}; \
|
||||
template<> inline std::string const type_name<ENUM> { #ENUM }
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Parse a value in postgres' text format as a TYPE.
|
||||
/** If the form of the value found in the string does not match the expected
|
||||
* type, e.g. if a decimal point is found when converting to an integer type,
|
||||
* the conversion fails. Overflows (e.g. converting "9999999999" to a 16-bit
|
||||
* C++ type) are also treated as errors. If in some cases this behaviour
|
||||
* should be inappropriate, convert to something bigger such as @c long @c int
|
||||
* first and then truncate the resulting value.
|
||||
*
|
||||
* Only the simplest possible conversions are supported. Fancy features like
|
||||
* hexadecimal or octal, spurious signs, or exponent notation won't work.
|
||||
* Whitespace is not stripped away. Only the kinds of strings that come out of
|
||||
* PostgreSQL and out of to_string() can be converted.
|
||||
*/
|
||||
template<typename TYPE>
|
||||
[[nodiscard]] inline TYPE from_string(std::string_view text)
|
||||
{
|
||||
return string_traits<TYPE>::from_string(text);
|
||||
}
|
||||
|
||||
|
||||
/// "Convert" a std::string_view to a std::string_view.
|
||||
/** Just returns its input.
|
||||
*
|
||||
* @warning Of course the result is only valid for as long as the original
|
||||
* string remains valid! Never access the string referenced by the return
|
||||
* value after the original has been destroyed.
|
||||
*/
|
||||
template<>
|
||||
[[nodiscard]] inline std::string_view from_string(std::string_view text)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
/// Attempt to convert postgres-generated string to given built-in object.
|
||||
/** This is like the single-argument form of the function, except instead of
|
||||
* returning the value, it sets @c value.
|
||||
*
|
||||
* You may find this more convenient in that it infers the type you want from
|
||||
* the argument you pass. But there are disadvantages: it requires an
|
||||
* assignment operator, and it may be less efficient.
|
||||
*/
|
||||
template<typename T> inline void from_string(std::string_view text, T &value)
|
||||
{
|
||||
value = from_string<T>(text);
|
||||
}
|
||||
|
||||
|
||||
/// Convert a value to a readable string that PostgreSQL will understand.
|
||||
/** The conversion does no special formatting, and ignores any locale settings.
|
||||
* The resulting string will be human-readable and in a format suitable for use
|
||||
* in SQL queries. It won't have niceties such as "thousands separators"
|
||||
* though.
|
||||
*/
|
||||
template<typename TYPE> inline std::string to_string(TYPE const &value);
|
||||
|
||||
|
||||
/// Convert multiple values to strings inside a single buffer.
|
||||
/** There must be enough room for all values, or this will throw
|
||||
* @c conversion_overrun. You can obtain a conservative estimate of the buffer
|
||||
* space required by calling @c size_buffer() on the values.
|
||||
*
|
||||
* The @c std::string_view results may point into the buffer, so don't assume
|
||||
* that they will remain valid after you destruct or move the buffer.
|
||||
*/
|
||||
template<typename... TYPE>
|
||||
[[nodiscard]] inline std::vector<std::string_view>
|
||||
to_buf(char *here, char const *end, TYPE... value)
|
||||
{
|
||||
return {[&here, end](auto v) {
|
||||
auto begin = here;
|
||||
here = string_traits<decltype(v)>::into_buf(begin, end, v);
|
||||
// Exclude the trailing zero out of the string_view.
|
||||
auto len{static_cast<std::size_t>(here - begin) - 1};
|
||||
return std::string_view{begin, len};
|
||||
}(value)...};
|
||||
}
|
||||
|
||||
/// Convert a value to a readable string that PostgreSQL will understand.
|
||||
/** This variant of to_string can sometimes save a bit of time in loops, by
|
||||
* re-using a std::string for multiple conversions.
|
||||
*/
|
||||
template<typename TYPE>
|
||||
inline void into_string(TYPE const &value, std::string &out);
|
||||
|
||||
|
||||
/// Is @c value null?
|
||||
template<typename TYPE>
|
||||
[[nodiscard]] inline constexpr bool is_null(TYPE const &value) noexcept
|
||||
{
|
||||
return nullness<strip_t<TYPE>>::is_null(value);
|
||||
}
|
||||
|
||||
|
||||
/// Estimate how much buffer space is needed to represent values as a string.
|
||||
/** The estimate may be a little pessimistic, if it saves time. It also
|
||||
* includes room for a terminating zero after each value.
|
||||
*/
|
||||
template<typename... TYPE>
|
||||
[[nodiscard]] inline std::size_t size_buffer(TYPE const &...value) noexcept
|
||||
{
|
||||
return (string_traits<strip_t<TYPE>>::size_buffer(value) + ...);
|
||||
}
|
||||
|
||||
|
||||
/// Does this type translate to an SQL array?
|
||||
/** Specialisations may override this to be true for container types.
|
||||
*
|
||||
* This may not always be a black-and-white choice. For instance, a
|
||||
* @c std::string is a container, but normally it translates to an SQL string,
|
||||
* not an SQL array.
|
||||
*/
|
||||
template<typename TYPE> inline constexpr bool is_sql_array{false};
|
||||
|
||||
|
||||
/// Can we use this type in arrays and composite types without quoting them?
|
||||
/** Define this as @c true only if values of @c TYPE can never contain any
|
||||
* special characters that might need escaping or confuse the parsing of array
|
||||
* or composite * types, such as commas, quotes, parentheses, braces, newlines,
|
||||
* and so on.
|
||||
*
|
||||
* When converting a value of such a type to a string in an array or a field in
|
||||
* a composite type, we do not need to add quotes, nor escape any special
|
||||
* characters.
|
||||
*
|
||||
* This is just an optimisation, so it defaults to @c false to err on the side
|
||||
* of slow correctness.
|
||||
*/
|
||||
template<typename TYPE> inline constexpr bool is_unquoted_safe{false};
|
||||
|
||||
|
||||
/// Element separator between SQL array elements of this type.
|
||||
template<typename T> inline constexpr char array_separator{','};
|
||||
|
||||
|
||||
/// What's the preferred format for passing non-null parameters of this type?
|
||||
/** This affects how we pass parameters of @c TYPE when calling parameterised
|
||||
* statements or prepared statements.
|
||||
*
|
||||
* Generally we pass parameters in text format, but binary strings are the
|
||||
* exception. We also pass nulls in binary format, so this function need not
|
||||
* handle null values.
|
||||
*/
|
||||
template<typename TYPE> inline constexpr format param_format(TYPE const &)
|
||||
{
|
||||
return format::text;
|
||||
}
|
||||
|
||||
|
||||
/// Implement @c string_traits<TYPE>::to_buf by calling @c into_buf.
|
||||
/** When you specialise @c string_traits for a new type, most of the time its
|
||||
* @c to_buf implementation has no special optimisation tricks and just writes
|
||||
* its text into the buffer it receives from the caller, starting at the
|
||||
* beginning.
|
||||
*
|
||||
* In that common situation, you can implement @c to_buf as just a call to
|
||||
* @c generic_to_buf. It will call @c into_buf and return the right result for
|
||||
* @c to_buf.
|
||||
*/
|
||||
template<typename TYPE>
|
||||
inline zview generic_to_buf(char *begin, char *end, TYPE const &value)
|
||||
{
|
||||
using traits = string_traits<TYPE>;
|
||||
// The trailing zero does not count towards the zview's size, so subtract 1
|
||||
// from the result we get from into_buf().
|
||||
if (is_null(value))
|
||||
return {};
|
||||
else
|
||||
return {begin, traits::into_buf(begin, end, value) - begin - 1};
|
||||
}
|
||||
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
/// Concept: Binary string, akin to @c std::string for binary data.
|
||||
/** Any type that satisfies this concept can represent an SQL BYTEA value.
|
||||
*
|
||||
* A @c binary has a @c begin(), @c end(), @c size(), and @data(). Each byte
|
||||
* is a @c std::byte, and they must all be laid out contiguously in memory so
|
||||
* we can reference them by a pointer.
|
||||
*/
|
||||
template<class TYPE>
|
||||
concept binary = std::ranges::contiguous_range<TYPE> and
|
||||
std::is_same_v<strip_t<value_type<TYPE>>, std::byte>;
|
||||
#endif
|
||||
//@}
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
#include "pqxx/internal/conversions.hxx"
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::stream_from class.
|
||||
*
|
||||
* pqxx::stream_from enables optimized batch reads from a database table.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/stream_from.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,361 @@
|
||||
/* Definition of the pqxx::stream_from class.
|
||||
*
|
||||
* pqxx::stream_from enables optimized batch reads from a database table.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stream_from 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_STREAM_FROM
|
||||
#define PQXX_H_STREAM_FROM
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <variant>
|
||||
|
||||
#include "pqxx/connection.hxx"
|
||||
#include "pqxx/except.hxx"
|
||||
#include "pqxx/internal/concat.hxx"
|
||||
#include "pqxx/internal/encoding_group.hxx"
|
||||
#include "pqxx/internal/stream_iterator.hxx"
|
||||
#include "pqxx/separated_list.hxx"
|
||||
#include "pqxx/transaction_focus.hxx"
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
class transaction_base;
|
||||
|
||||
|
||||
/// Pass this to a `stream_from` constructor to stream table contents.
|
||||
/** @deprecated Use @ref stream_from::table() instead.
|
||||
*/
|
||||
constexpr from_table_t from_table;
|
||||
/// Pass this to a `stream_from` constructor to stream query results.
|
||||
/** @deprecated Use stream_from::query() instead.
|
||||
*/
|
||||
constexpr from_query_t from_query;
|
||||
|
||||
|
||||
/// Stream data from the database.
|
||||
/** For larger data sets, retrieving data this way is likely to be faster than
|
||||
* executing a query and then iterating and converting the rows fields. You
|
||||
* will also be able to start processing before all of the data has come in.
|
||||
*
|
||||
* There are also downsides. Not all kinds of query will work in a stream.
|
||||
* But straightforward `SELECT` and `UPDATE ... RETURNING` queries should work.
|
||||
* This function makes use of @ref pqxx::stream_from, which in turn uses
|
||||
* PostgreSQL's `COPY` command, so see the documentation for those to get the
|
||||
* full details.
|
||||
*
|
||||
* There are other downsides. If there stream encounters an error, it may
|
||||
* leave the entire connection in an unusable state, so you'll have to give the
|
||||
* whole thing up. Finally, opening a stream puts the connection in a special
|
||||
* state, so you won't be able to do many other things with the connection or
|
||||
* the transaction while the stream is open.
|
||||
*
|
||||
* There are two ways of starting a stream: you stream either all rows in a
|
||||
* table (using one of the factories, `table()` or `raw_table()`), or the
|
||||
* results of a query (using the `query()` factory).
|
||||
*
|
||||
* Usually you'll want the `stream` convenience wrapper in
|
||||
* @ref transaction_base, * so you don't need to deal with this class directly.
|
||||
*
|
||||
* @warning While a stream is active, you cannot execute queries, open a
|
||||
* pipeline, 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 stream_from : transaction_focus
|
||||
{
|
||||
public:
|
||||
using raw_line =
|
||||
std::pair<std::unique_ptr<char, std::function<void(char *)>>, std::size_t>;
|
||||
|
||||
/// Factory: Execute query, and stream the results.
|
||||
/** The query can be a SELECT query or a VALUES query; or it can be an
|
||||
* UPDATE, INSERT, or DELETE with a RETURNING clause.
|
||||
*
|
||||
* The query is executed as part of a COPY statement, so there are additional
|
||||
* restrictions on what kind of query you can use here. See the PostgreSQL
|
||||
* documentation for the COPY command:
|
||||
*
|
||||
* https://www.postgresql.org/docs/current/sql-copy.html
|
||||
*/
|
||||
static stream_from query(transaction_base &tx, std::string_view q)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return {tx, from_query, q};
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Streaming data from tables
|
||||
*
|
||||
* You can use `stream_from` to read a table's contents. This is a quick
|
||||
* and easy way to read a table, but it comes with limitations. It cannot
|
||||
* stream from a view, only from a table. It does not support conditions.
|
||||
* And there are no guarantees about ordering. If you need any of those
|
||||
* things, consider streaming from a query instead.
|
||||
*/
|
||||
//@{
|
||||
|
||||
/// Factory: Stream data from a pre-quoted table and columns.
|
||||
/** Use this factory if you need to create multiple streams using the same
|
||||
* table path and/or columns list, and you want to save a bit of work on
|
||||
* composing the internal SQL statement for starting the stream. It lets you
|
||||
* compose the string representations for the table path and the columns
|
||||
* list, so you can compute these once and then re-use them later.
|
||||
*
|
||||
* @param tx The transaction within which the stream will operate.
|
||||
* @param path Name or path for the table upon which the stream will
|
||||
* operate. If any part of the table path may contain special
|
||||
* characters or be case-sensitive, quote the path using
|
||||
* pqxx::connection::quote_table().
|
||||
* @param columns Columns which the stream will read. They should be
|
||||
* comma-separated and, if needed, quoted. You can produce the string
|
||||
* using pqxx::connection::quote_columns(). If you omit this argument,
|
||||
* the stream will read all columns in the table, in schema order.
|
||||
*/
|
||||
static stream_from raw_table(
|
||||
transaction_base &tx, std::string_view path,
|
||||
std::string_view columns = ""sv);
|
||||
|
||||
/// Factory: Stream data from a given table.
|
||||
/** This is the convenient way to stream from a table.
|
||||
*/
|
||||
static stream_from table(
|
||||
transaction_base &tx, table_path path,
|
||||
std::initializer_list<std::string_view> columns = {});
|
||||
//@}
|
||||
|
||||
/// Execute query, and stream over the results.
|
||||
/** @deprecated Use factory function @ref query instead.
|
||||
*/
|
||||
[[deprecated("Use query() factory instead.")]] stream_from(
|
||||
transaction_base &, from_query_t, std::string_view query);
|
||||
|
||||
/// Stream all rows in table, all columns.
|
||||
/** @deprecated Use factories @ref table or @ref raw_table instead.
|
||||
*/
|
||||
[[deprecated("Use table() or raw_table() factory instead.")]] stream_from(
|
||||
transaction_base &, from_table_t, std::string_view table);
|
||||
|
||||
/// Stream given columns from all rows in table.
|
||||
/** @deprecated Use factories @ref table or @ref raw_table instead.
|
||||
*/
|
||||
template<typename Iter>
|
||||
[[deprecated("Use table() or raw_table() factory instead.")]] stream_from(
|
||||
transaction_base &, from_table_t, std::string_view table,
|
||||
Iter columns_begin, Iter columns_end);
|
||||
|
||||
/// Stream given columns from all rows in table.
|
||||
/** @deprecated Use factory function @ref query instead.
|
||||
*/
|
||||
template<typename Columns>
|
||||
[[deprecated("Use table() or raw_table() factory instead.")]] stream_from(
|
||||
transaction_base &tx, from_table_t, std::string_view table,
|
||||
Columns const &columns);
|
||||
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
/// @deprecated Use factories @ref table or @ref raw_table instead.
|
||||
[[deprecated("Use the from_table_t overload instead.")]] stream_from(
|
||||
transaction_base &tx, std::string_view table) :
|
||||
stream_from{tx, from_table, table}
|
||||
{}
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
|
||||
/// @deprecated Use factories @ref table or @ref raw_table instead.
|
||||
template<typename Columns>
|
||||
[[deprecated("Use the from_table_t overload instead.")]] stream_from(
|
||||
transaction_base &tx, std::string_view table, Columns const &columns) :
|
||||
stream_from{tx, from_table, table, columns}
|
||||
{}
|
||||
|
||||
/// @deprecated Use factories @ref table or @ref raw_table instead.
|
||||
template<typename Iter>
|
||||
[[deprecated("Use the from_table_t overload instead.")]] stream_from(
|
||||
transaction_base &, std::string_view table, Iter columns_begin,
|
||||
Iter columns_end);
|
||||
|
||||
~stream_from() noexcept;
|
||||
|
||||
/// May this stream still produce more data?
|
||||
[[nodiscard]] constexpr operator bool() const noexcept
|
||||
{
|
||||
return not m_finished;
|
||||
}
|
||||
/// Has this stream produced all the data it is going to produce?
|
||||
[[nodiscard]] constexpr bool operator!() const noexcept
|
||||
{
|
||||
return m_finished;
|
||||
}
|
||||
|
||||
/// Finish this stream. Call this before continuing to use the connection.
|
||||
/** Consumes all remaining lines, and closes the stream.
|
||||
*
|
||||
* This may take a while if you're abandoning the stream before it's done, so
|
||||
* skip it in error scenarios where you're not planning to use the connection
|
||||
* again afterwards.
|
||||
*/
|
||||
void complete();
|
||||
|
||||
/// Read one row into a tuple.
|
||||
/** Converts the row's fields into the fields making up the tuple.
|
||||
*
|
||||
* For a column which can contain nulls, be sure to give the corresponding
|
||||
* tuple field a type which can be null. For example, to read a field as
|
||||
* `int` when it may contain nulls, read it as `std::optional<int>`.
|
||||
* Using `std::shared_ptr` or `std::unique_ptr` will also work.
|
||||
*/
|
||||
template<typename Tuple> stream_from &operator>>(Tuple &);
|
||||
|
||||
/// Doing this with a `std::variant` is going to be horrifically borked.
|
||||
template<typename... Vs>
|
||||
stream_from &operator>>(std::variant<Vs...> &) = delete;
|
||||
|
||||
/// Iterate over this stream. Supports range-based "for" loops.
|
||||
/** Produces an input iterator over the stream.
|
||||
*
|
||||
* Do not call this yourself. Use it like "for (auto data : stream.iter())".
|
||||
*/
|
||||
template<typename... TYPE> [[nodiscard]] auto iter() &
|
||||
{
|
||||
return pqxx::internal::stream_input_iteration<TYPE...>{*this};
|
||||
}
|
||||
|
||||
/// Read a row. Return fields as views, valid until you read the next row.
|
||||
/** Returns `nullptr` when there are no more rows to read. Do not attempt
|
||||
* to read any further rows after that.
|
||||
*
|
||||
* Do not access the vector, or the storage referenced by the views, after
|
||||
* closing or completing the stream, or after attempting to read a next row.
|
||||
*
|
||||
* A @ref pqxx::zview is like a `std::string_view`, but with the added
|
||||
* guarantee that if its data pointer is non-null, the string is followed by
|
||||
* a terminating zero (which falls just outside the view itself).
|
||||
*
|
||||
* If any of the views' data pointer is null, that means that the
|
||||
* corresponding SQL field is null.
|
||||
*
|
||||
* @warning The return type may change in the future, to support C++20
|
||||
* coroutine-based usage.
|
||||
*/
|
||||
std::vector<zview> const *read_row() &;
|
||||
|
||||
/// Read a raw line of text from the COPY command.
|
||||
/** @warning Do not use this unless you really know what you're doing. */
|
||||
raw_line get_raw_line();
|
||||
|
||||
private:
|
||||
// TODO: Clean up this signature once we cull the deprecated constructors.
|
||||
/// @deprecated
|
||||
stream_from(
|
||||
transaction_base &tx, std::string_view table, std::string_view columns,
|
||||
from_table_t);
|
||||
|
||||
// TODO: Clean up this signature once we cull the deprecated constructors.
|
||||
/// @deprecated
|
||||
stream_from(
|
||||
transaction_base &, std::string_view unquoted_table,
|
||||
std::string_view columns, from_table_t, int);
|
||||
|
||||
template<typename Tuple, std::size_t... indexes>
|
||||
void extract_fields(Tuple &t, std::index_sequence<indexes...>) const
|
||||
{
|
||||
(extract_value<Tuple, indexes>(t), ...);
|
||||
}
|
||||
|
||||
pqxx::internal::glyph_scanner_func *m_glyph_scanner;
|
||||
|
||||
/// Current row's fields' text, combined into one reusable string.
|
||||
std::string m_row;
|
||||
|
||||
/// The current row's fields.
|
||||
std::vector<zview> m_fields;
|
||||
|
||||
bool m_finished = false;
|
||||
|
||||
void close();
|
||||
|
||||
template<typename Tuple, std::size_t index>
|
||||
void extract_value(Tuple &) const;
|
||||
|
||||
/// Read a line of COPY data, write `m_row` and `m_fields`.
|
||||
void parse_line();
|
||||
};
|
||||
|
||||
|
||||
template<typename Columns>
|
||||
inline stream_from::stream_from(
|
||||
transaction_base &tx, from_table_t, std::string_view table_name,
|
||||
Columns const &columns) :
|
||||
stream_from{
|
||||
tx, from_table, table_name, std::begin(columns), std::end(columns)}
|
||||
{}
|
||||
|
||||
|
||||
template<typename Iter>
|
||||
inline stream_from::stream_from(
|
||||
transaction_base &tx, from_table_t, std::string_view table,
|
||||
Iter columns_begin, Iter columns_end) :
|
||||
stream_from{
|
||||
tx, table, separated_list(",", columns_begin, columns_end),
|
||||
from_table, 1}
|
||||
{}
|
||||
|
||||
|
||||
template<typename Tuple> inline stream_from &stream_from::operator>>(Tuple &t)
|
||||
{
|
||||
if (m_finished)
|
||||
return *this;
|
||||
static constexpr auto tup_size{std::tuple_size_v<Tuple>};
|
||||
m_fields.reserve(tup_size);
|
||||
parse_line();
|
||||
if (m_finished)
|
||||
return *this;
|
||||
|
||||
if (std::size(m_fields) != tup_size)
|
||||
throw usage_error{internal::concat(
|
||||
"Tried to extract ", tup_size, " field(s) from a stream of ",
|
||||
std::size(m_fields), ".")};
|
||||
|
||||
extract_fields(t, std::make_index_sequence<tup_size>{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
template<typename Tuple, std::size_t index>
|
||||
inline void stream_from::extract_value(Tuple &t) const
|
||||
{
|
||||
using field_type = strip_t<decltype(std::get<index>(t))>;
|
||||
using nullity = nullness<field_type>;
|
||||
assert(index < std::size(m_fields));
|
||||
if constexpr (nullity::always_null)
|
||||
{
|
||||
if (std::data(m_fields[index]) != nullptr)
|
||||
throw conversion_error{"Streaming non-null value into null field."};
|
||||
}
|
||||
else if (std::data(m_fields[index]) == nullptr)
|
||||
{
|
||||
if constexpr (nullity::has_null)
|
||||
std::get<index>(t) = nullity::null();
|
||||
else
|
||||
internal::throw_null_conversion(type_name<field_type>);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't ever try to convert a non-null value to nullptr_t!
|
||||
std::get<index>(t) = from_string<field_type>(m_fields[index]);
|
||||
}
|
||||
}
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::stream_to class.
|
||||
*
|
||||
* pqxx::stream_to enables optimized batch updates to a database table.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/stream_to.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,455 @@
|
||||
/* Definition of the pqxx::stream_to class.
|
||||
*
|
||||
* pqxx::stream_to enables optimized batch updates to a database table.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/stream_to.hxx 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_STREAM_TO
|
||||
#define PQXX_H_STREAM_TO
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/separated_list.hxx"
|
||||
#include "pqxx/transaction_base.hxx"
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Efficiently write data directly to a database table.
|
||||
/** If you wish to insert rows of data into a table, you can compose INSERT
|
||||
* statements and execute them. But it's slow and tedious, and you need to
|
||||
* worry about quoting and escaping the data.
|
||||
*
|
||||
* If you're just inserting a single row, it probably won't matter much. You
|
||||
* can use prepared or parameterised statements to take care of the escaping
|
||||
* for you. But if you're inserting large numbers of rows you will want
|
||||
* something better.
|
||||
*
|
||||
* Inserting rows one by one using INSERT statements involves a lot of
|
||||
* pointless overhead, especially when you are working with a remote database
|
||||
* server over the network. You may end up sending each row over the network
|
||||
* as a separate query, and waiting for a reply. Do it "in bulk" using
|
||||
* `stream_to`, and you may find that it goes many times faster. Sometimes
|
||||
* you gain orders of magnitude in speed.
|
||||
*
|
||||
* Here's how it works: you create a `stream_to` stream to start writing to
|
||||
* your table. You will probably want to specify the columns. Then, you
|
||||
* feed your data into the stream one row at a time. And finally, you call the
|
||||
* stream's @ref complete function to tell it to finalise the operation, wait
|
||||
* for completion, and check for errors.
|
||||
*
|
||||
* (You _must_ complete the stream before committing or aborting the
|
||||
* transaction. The connection is in a special state while the stream is
|
||||
* active, where it can't process commands, and can't commit or abort a
|
||||
* transaction.)
|
||||
*
|
||||
* So how do you feed a row of data into the stream? There's several ways, but
|
||||
* the preferred one is to call its @ref write_values. Pass the field values
|
||||
* as arguments. Doesn't matter what type they are, as long as libpqxx knows
|
||||
* how to convert them to PostgreSQL's text format: `int`, `std::string` or
|
||||
* `std:string_view`, `float` and `double`, `bool`... lots of basic types
|
||||
* are supported. If some of the values are null, feel free to use
|
||||
* `std::optional`, `std::shared_ptr`, or `std::unique_ptr`.
|
||||
*
|
||||
* The arguments' types don't even have to match the fields' SQL types. If you
|
||||
* want to insert an `int` into a `DECIMAL` column, that's your choice -- it
|
||||
* will produce a `DECIMAL` value which happens to be integral. Insert a
|
||||
* `float` into a `VARCHAR` column? That's fine, you'll get a string whose
|
||||
* contents happen to read like a number. And so on. You can even insert
|
||||
* different types of value in the same column on different rows. If you have
|
||||
* a code path where a particular field is always null, just insert `nullptr`.
|
||||
*
|
||||
* There is another way to insert rows: the `<<` ("shift-left") operator.
|
||||
* It's not as fast and it doesn't support variable arguments: each row must be
|
||||
* either a `std::tuple` or something iterable, such as a `std::vector`, or
|
||||
* anything else with a `begin()` and `end()`.
|
||||
*
|
||||
* @warning While a stream is active, you cannot execute queries, open a
|
||||
* pipeline, 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 stream_to : transaction_focus
|
||||
{
|
||||
public:
|
||||
/// Stream data to a pre-quoted table and columns.
|
||||
/** This factory can be useful when it's not convenient to provide the
|
||||
* columns list in the form of a `std::initializer_list`, or when the list
|
||||
* of columns is simply not known at compile time.
|
||||
*
|
||||
* Also use this if you need to create multiple streams using the same table
|
||||
* path and/or columns list, and you want to save a bit of work on composing
|
||||
* the internal SQL statement for starting the stream. It lets you compose
|
||||
* the string representations for the table path and the columns list, so you
|
||||
* can compute these once and then re-use them later.
|
||||
*
|
||||
* @param tx The transaction within which the stream will operate.
|
||||
* @param path Name or path for the table upon which the stream will
|
||||
* operate. If any part of the table path may contain special
|
||||
* characters or be case-sensitive, quote the path using
|
||||
* pqxx::connection::quote_table().
|
||||
* @param columns Columns to which the stream will write. They should be
|
||||
* comma-separated and, if needed, quoted. You can produce the string
|
||||
* using pqxx::connection::quote_columns(). If you omit this argument,
|
||||
* the stream will write all columns in the table, in schema order.
|
||||
*/
|
||||
static stream_to raw_table(
|
||||
transaction_base &tx, std::string_view path, std::string_view columns = "")
|
||||
{
|
||||
return {tx, path, columns};
|
||||
}
|
||||
|
||||
/// Create a `stream_to` writing to a named table and columns.
|
||||
/** Use this to stream data to a table, where the list of columns is known at
|
||||
* compile time.
|
||||
*
|
||||
* @param tx The transaction within which the stream will operate.
|
||||
* @param path A @ref table_path designating the target table.
|
||||
* @param columns Optionally, the columns to which the stream should write.
|
||||
* If you do not pass this, the stream will write to all columns in the
|
||||
* table, in schema order.
|
||||
*/
|
||||
static stream_to table(
|
||||
transaction_base &tx, table_path path,
|
||||
std::initializer_list<std::string_view> columns = {})
|
||||
{
|
||||
auto const &conn{tx.conn()};
|
||||
return raw_table(tx, conn.quote_table(path), conn.quote_columns(columns));
|
||||
}
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
/// Create a `stream_to` writing to a named table and columns.
|
||||
/** Use this version to stream data to a table, when the list of columns is
|
||||
* not known at compile time.
|
||||
*
|
||||
* @param tx The transaction within which the stream will operate.
|
||||
* @param path A @ref table_path designating the target table.
|
||||
* @param columns The columns to which the stream should write.
|
||||
*/
|
||||
template<PQXX_CHAR_STRINGS_ARG COLUMNS>
|
||||
static stream_to
|
||||
table(transaction_base &tx, table_path path, COLUMNS const &columns)
|
||||
{
|
||||
auto const &conn{tx.conn()};
|
||||
return stream_to::raw_table(
|
||||
tx, conn.quote_table(path), tx.conn().quote_columns(columns));
|
||||
}
|
||||
|
||||
/// Create a `stream_to` writing to a named table and columns.
|
||||
/** Use this version to stream data to a table, when the list of columns is
|
||||
* not known at compile time.
|
||||
*
|
||||
* @param tx The transaction within which the stream will operate.
|
||||
* @param path A @ref table_path designating the target table.
|
||||
* @param columns The columns to which the stream should write.
|
||||
*/
|
||||
template<PQXX_CHAR_STRINGS_ARG COLUMNS>
|
||||
static stream_to
|
||||
table(transaction_base &tx, std::string_view path, COLUMNS const &columns)
|
||||
{
|
||||
return stream_to::raw_table(tx, path, tx.conn().quote_columns(columns));
|
||||
}
|
||||
#endif // PQXX_HAVE_CONCEPTS
|
||||
|
||||
/// Create a stream, without specifying columns.
|
||||
/** @deprecated Use @ref table or @ref raw_table as a factory.
|
||||
*
|
||||
* Fields will be inserted in whatever order the columns have in the
|
||||
* database.
|
||||
*
|
||||
* You'll probably want to specify the columns, so that the mapping between
|
||||
* your data fields and the table is explicit in your code, and not hidden
|
||||
* in an "implicit contract" between your code and your schema.
|
||||
*/
|
||||
[[deprecated("Use table() or raw_table() factory.")]] stream_to(
|
||||
transaction_base &tx, std::string_view table_name) :
|
||||
stream_to{tx, table_name, ""sv}
|
||||
{}
|
||||
|
||||
/// Create a stream, specifying column names as a container of strings.
|
||||
/** @deprecated Use @ref table or @ref raw_table as a factory.
|
||||
*/
|
||||
template<typename Columns>
|
||||
[[deprecated("Use table() or raw_table() factory.")]] stream_to(
|
||||
transaction_base &, std::string_view table_name, Columns const &columns);
|
||||
|
||||
/// Create a stream, specifying column names as a sequence of strings.
|
||||
/** @deprecated Use @ref table or @ref raw_table as a factory.
|
||||
*/
|
||||
template<typename Iter>
|
||||
[[deprecated("Use table() or raw_table() factory.")]] stream_to(
|
||||
transaction_base &, std::string_view table_name, Iter columns_begin,
|
||||
Iter columns_end);
|
||||
|
||||
~stream_to() noexcept;
|
||||
|
||||
/// Does this stream still need to @ref complete()?
|
||||
[[nodiscard]] constexpr operator bool() const noexcept
|
||||
{
|
||||
return not m_finished;
|
||||
}
|
||||
/// Has this stream been through its concluding @c complete()?
|
||||
[[nodiscard]] constexpr bool operator!() const noexcept
|
||||
{
|
||||
return m_finished;
|
||||
}
|
||||
|
||||
/// Complete the operation, and check for errors.
|
||||
/** Always call this to close the stream in an orderly fashion, even after
|
||||
* an error. (In the case of an error, abort the transaction afterwards.)
|
||||
*
|
||||
* The only circumstance where it's safe to skip this is after an error, if
|
||||
* you're discarding the entire connection.
|
||||
*/
|
||||
void complete();
|
||||
|
||||
/// Insert a row of data.
|
||||
/** Returns a reference to the stream, so you can chain the calls.
|
||||
*
|
||||
* The @c row can be a tuple, or any type that can be iterated. Each
|
||||
* item becomes a field in the row, in the same order as the columns you
|
||||
* specified when creating the stream.
|
||||
*
|
||||
* If you don't already happen to have your fields in the form of a tuple or
|
||||
* container, prefer @c write_values. It's faster and more convenient.
|
||||
*/
|
||||
template<typename Row> stream_to &operator<<(Row const &row)
|
||||
{
|
||||
write_row(row);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Stream a `stream_from` straight into a `stream_to`.
|
||||
/** This can be useful when copying between different databases. If the
|
||||
* source and the destination are on the same database, you'll get better
|
||||
* performance doing it all in a regular query.
|
||||
*/
|
||||
stream_to &operator<<(stream_from &);
|
||||
|
||||
/// Insert a row of data, given in the form of a @c std::tuple or container.
|
||||
/** The @c row can be a tuple, or any type that can be iterated. Each
|
||||
* item becomes a field in the row, in the same order as the columns you
|
||||
* specified when creating the stream.
|
||||
*
|
||||
* The preferred way to insert a row is @c write_values.
|
||||
*/
|
||||
template<typename Row> void write_row(Row const &row)
|
||||
{
|
||||
fill_buffer(row);
|
||||
write_buffer();
|
||||
}
|
||||
|
||||
/// Insert values as a row.
|
||||
/** This is the recommended way of inserting data. Pass your field values,
|
||||
* of any convertible type.
|
||||
*/
|
||||
template<typename... Ts> void write_values(Ts const &...fields)
|
||||
{
|
||||
fill_buffer(fields...);
|
||||
write_buffer();
|
||||
}
|
||||
|
||||
private:
|
||||
/// Stream a pre-quoted table name and columns list.
|
||||
stream_to(
|
||||
transaction_base &tx, std::string_view path, std::string_view columns);
|
||||
|
||||
bool m_finished = false;
|
||||
|
||||
/// Reusable buffer for a row. Saves doing an allocation for each row.
|
||||
std::string m_buffer;
|
||||
|
||||
/// Reusable buffer for converting/escaping a field.
|
||||
std::string m_field_buf;
|
||||
|
||||
/// Glyph scanner, for parsing the client encoding.
|
||||
internal::glyph_scanner_func *m_scanner;
|
||||
|
||||
/// Write a row of raw text-format data into the destination table.
|
||||
void write_raw_line(std::string_view);
|
||||
|
||||
/// Write a row of data from @c m_buffer into the destination table.
|
||||
/** Resets the buffer for the next row.
|
||||
*/
|
||||
void write_buffer();
|
||||
|
||||
/// COPY encoding for a null field, plus subsequent separator.
|
||||
static constexpr std::string_view null_field{"\\N\t"};
|
||||
|
||||
/// Estimate buffer space needed for a field which is always null.
|
||||
template<typename T>
|
||||
static std::enable_if_t<nullness<T>::always_null, std::size_t>
|
||||
estimate_buffer(T const &)
|
||||
{
|
||||
return std::size(null_field);
|
||||
}
|
||||
|
||||
/// Estimate buffer space needed for field f.
|
||||
/** The estimate is not very precise. We don't actually know how much space
|
||||
* we'll need once the escaping comes in.
|
||||
*/
|
||||
template<typename T>
|
||||
static std::enable_if_t<not nullness<T>::always_null, std::size_t>
|
||||
estimate_buffer(T const &field)
|
||||
{
|
||||
return is_null(field) ? std::size(null_field) : size_buffer(field);
|
||||
}
|
||||
|
||||
/// Append escaped version of @c data to @c m_buffer, plus a tab.
|
||||
void escape_field_to_buffer(std::string_view data);
|
||||
|
||||
/// Append string representation for @c f to @c m_buffer.
|
||||
/** This is for the general case, where the field may contain a value.
|
||||
*
|
||||
* Also appends a tab. The tab is meant to be a separator, not a terminator,
|
||||
* so if you write any fields at all, you'll end up with one tab too many
|
||||
* at the end of the buffer.
|
||||
*/
|
||||
template<typename Field>
|
||||
std::enable_if_t<not nullness<Field>::always_null>
|
||||
append_to_buffer(Field const &f)
|
||||
{
|
||||
// We append each field, terminated by a tab. That will leave us with
|
||||
// one tab too many, assuming we write any fields at all; we remove that
|
||||
// at the end.
|
||||
if (is_null(f))
|
||||
{
|
||||
// Easy. Append null and tab in one go.
|
||||
m_buffer.append(null_field);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Convert f into m_buffer.
|
||||
|
||||
using traits = string_traits<Field>;
|
||||
auto const budget{estimate_buffer(f)};
|
||||
auto const offset{std::size(m_buffer)};
|
||||
|
||||
if constexpr (std::is_arithmetic_v<Field>)
|
||||
{
|
||||
// Specially optimised for "safe" types, which never need any
|
||||
// escaping. Convert straight into m_buffer.
|
||||
|
||||
// The budget we get from size_buffer() includes room for the trailing
|
||||
// zero, which we must remove. But we're also inserting tabs between
|
||||
// fields, so we re-purpose the extra byte for that.
|
||||
auto const total{offset + budget};
|
||||
m_buffer.resize(total);
|
||||
auto const data{m_buffer.data()};
|
||||
char *const end{traits::into_buf(data + offset, data + total, f)};
|
||||
*(end - 1) = '\t';
|
||||
// Shrink to fit. Keep the tab though.
|
||||
m_buffer.resize(static_cast<std::size_t>(end - data));
|
||||
}
|
||||
else if constexpr (
|
||||
std::is_same_v<Field, std::string> or
|
||||
std::is_same_v<Field, std::string_view> or
|
||||
std::is_same_v<Field, zview>)
|
||||
{
|
||||
// This string may need escaping.
|
||||
m_field_buf.resize(budget);
|
||||
escape_field_to_buffer(f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This field needs to be converted to a string, and after that,
|
||||
// escaped as well.
|
||||
m_field_buf.resize(budget);
|
||||
auto const data{m_field_buf.data()};
|
||||
escape_field_to_buffer(
|
||||
traits::to_buf(data, data + std::size(m_field_buf), f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Append string representation for a null field to @c m_buffer.
|
||||
/** This special case is for types which are always null.
|
||||
*
|
||||
* Also appends a tab. The tab is meant to be a separator, not a terminator,
|
||||
* so if you write any fields at all, you'll end up with one tab too many
|
||||
* at the end of the buffer.
|
||||
*/
|
||||
template<typename Field>
|
||||
std::enable_if_t<nullness<Field>::always_null>
|
||||
append_to_buffer(Field const &)
|
||||
{
|
||||
m_buffer.append(null_field);
|
||||
}
|
||||
|
||||
/// Write raw COPY line into @c m_buffer, based on a container of fields.
|
||||
template<typename Container>
|
||||
std::enable_if_t<not std::is_same_v<typename Container::value_type, char>>
|
||||
fill_buffer(Container const &c)
|
||||
{
|
||||
// To avoid unnecessary allocations and deallocations, we run through c
|
||||
// twice: once to determine how much buffer space we may need, and once to
|
||||
// actually write it into the buffer.
|
||||
std::size_t budget{0};
|
||||
for (auto const &f : c) budget += estimate_buffer(f);
|
||||
m_buffer.reserve(budget);
|
||||
for (auto const &f : c) append_to_buffer(f);
|
||||
}
|
||||
|
||||
/// Estimate how many buffer bytes we need to write tuple.
|
||||
template<typename Tuple, std::size_t... indexes>
|
||||
static std::size_t
|
||||
budget_tuple(Tuple const &t, std::index_sequence<indexes...>)
|
||||
{
|
||||
return (estimate_buffer(std::get<indexes>(t)) + ...);
|
||||
}
|
||||
|
||||
/// Write tuple of fields to @c m_buffer.
|
||||
template<typename Tuple, std::size_t... indexes>
|
||||
void append_tuple(Tuple const &t, std::index_sequence<indexes...>)
|
||||
{
|
||||
(append_to_buffer(std::get<indexes>(t)), ...);
|
||||
}
|
||||
|
||||
/// Write raw COPY line into @c m_buffer, based on a tuple of fields.
|
||||
template<typename... Elts> void fill_buffer(std::tuple<Elts...> const &t)
|
||||
{
|
||||
using indexes = std::make_index_sequence<sizeof...(Elts)>;
|
||||
|
||||
m_buffer.reserve(budget_tuple(t, indexes{}));
|
||||
append_tuple(t, indexes{});
|
||||
}
|
||||
|
||||
/// Write raw COPY line into @c m_buffer, based on varargs fields.
|
||||
template<typename... Ts> void fill_buffer(const Ts &...fields)
|
||||
{
|
||||
(..., append_to_buffer(fields));
|
||||
}
|
||||
|
||||
constexpr static std::string_view s_classname{"stream_to"};
|
||||
};
|
||||
|
||||
|
||||
template<typename Columns>
|
||||
inline stream_to::stream_to(
|
||||
transaction_base &tx, std::string_view table_name, Columns const &columns) :
|
||||
stream_to{tx, table_name, std::begin(columns), std::end(columns)}
|
||||
{}
|
||||
|
||||
|
||||
template<typename Iter>
|
||||
inline stream_to::stream_to(
|
||||
transaction_base &tx, std::string_view table_name, Iter columns_begin,
|
||||
Iter columns_end) :
|
||||
stream_to{
|
||||
tx,
|
||||
tx.quote_name(
|
||||
table_name,
|
||||
separated_list(",", columns_begin, columns_end, [&tx](auto col) {
|
||||
return tx.quote_name(*col);
|
||||
}))}
|
||||
{}
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::subtransaction class.
|
||||
*
|
||||
* pqxx::subtransaction is a nested transaction, i.e. one inside a transaction.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/subtransaction.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,96 @@
|
||||
/* Definition of the pqxx::subtransaction class.
|
||||
*
|
||||
* pqxx::subtransaction is a nested transaction, i.e. one within a transaction.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/subtransaction 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_SUBTRANSACTION
|
||||
#define PQXX_H_SUBTRANSACTION
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/dbtransaction.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/**
|
||||
* @ingroup transactions
|
||||
*/
|
||||
/// "Transaction" nested within another transaction
|
||||
/** A subtransaction can be executed inside a backend transaction, or inside
|
||||
* another subtransaction. This can be useful when, for example, statements in
|
||||
* a transaction may harmlessly fail and you don't want them to abort the
|
||||
* entire transaction. Here's an example of how a temporary table may be
|
||||
* dropped before re-creating it, without failing if the table did not exist:
|
||||
*
|
||||
* ```cxx
|
||||
* void do_job(connection &C)
|
||||
* {
|
||||
* string const temptable = "fleetingtable";
|
||||
*
|
||||
* work W(C, "do_job");
|
||||
* do_firstpart(W);
|
||||
*
|
||||
* // Attempt to delete our temporary table if it already existed.
|
||||
* try
|
||||
* {
|
||||
* subtransaction S(W, "droptemp");
|
||||
* S.exec0("DROP TABLE " + temptable);
|
||||
* S.commit();
|
||||
* }
|
||||
* catch (undefined_table const &)
|
||||
* {
|
||||
* // Table did not exist. Which is what we were hoping to achieve anyway.
|
||||
* // Carry on without regrets.
|
||||
* }
|
||||
*
|
||||
* // S may have gone into a failed state and been destroyed, but the
|
||||
* // upper-level transaction W is still fine. We can continue to use it.
|
||||
* W.exec0("CREATE TEMP TABLE " + temptable + "(bar integer, splat
|
||||
* varchar)");
|
||||
*
|
||||
* do_lastpart(W);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* (This is just an example. If you really wanted to do drop a table without
|
||||
* an error if it doesn't exist, you'd use DROP TABLE IF EXISTS.)
|
||||
*
|
||||
* There are no isolation levels inside a transaction. They are not needed
|
||||
* because all actions within the same backend transaction are always performed
|
||||
* sequentially anyway.
|
||||
*
|
||||
* @warning While the subtransaction is "live," you cannot execute queries or
|
||||
* open streams etc. on its parent 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 subtransaction : public transaction_focus,
|
||||
public dbtransaction
|
||||
{
|
||||
public:
|
||||
/// Nest a subtransaction nested in another transaction.
|
||||
explicit subtransaction(dbtransaction &t, std::string_view tname = ""sv);
|
||||
|
||||
/// Nest a subtransaction in another subtransaction.
|
||||
explicit subtransaction(subtransaction &t, std::string_view name = ""sv);
|
||||
|
||||
virtual ~subtransaction() noexcept override;
|
||||
|
||||
private:
|
||||
std::string quoted_name() const
|
||||
{
|
||||
return quote_name(transaction_focus::name());
|
||||
}
|
||||
virtual void do_commit() override;
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
/** Date/time string conversions.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/time.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,88 @@
|
||||
/** Support for date/time values.
|
||||
*
|
||||
* At the moment this supports dates, but not times.
|
||||
*/
|
||||
#ifndef PQXX_H_TIME
|
||||
#define PQXX_H_TIME
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "pqxx/internal/concat.hxx"
|
||||
#include "pqxx/strconv.hxx"
|
||||
|
||||
|
||||
#if defined(PQXX_HAVE_YEAR_MONTH_DAY)
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
template<>
|
||||
struct nullness<std::chrono::year_month_day>
|
||||
: no_null<std::chrono::year_month_day>
|
||||
{};
|
||||
|
||||
|
||||
/// String representation for a Gregorian date in ISO-8601 format.
|
||||
/** @warning Experimental. There may still be design problems, particularly
|
||||
* when it comes to BC years.
|
||||
*
|
||||
* PostgreSQL supports a choice of date formats, but libpqxx does not. The
|
||||
* other formats in turn support a choice of "month before day" versus "day
|
||||
* before month," meaning that it's not necessarily known which format a given
|
||||
* date is supposed to be. So I repeat: ISO-8601-style format only!
|
||||
*
|
||||
* Invalid dates will not convert. This includes February 29 on non-leap
|
||||
* years, which is why it matters that `year_month_day` represents a
|
||||
* _Gregorian_ date.
|
||||
*
|
||||
* The range of years is limited. At the time of writing, PostgreSQL 14
|
||||
* supports years from 4713 BC to 294276 AD inclusive, and C++20 supports
|
||||
* a range of 32767 BC to 32767 AD inclusive. So in practice, years must fall
|
||||
* between 4713 BC and 32767 AD, inclusive.
|
||||
*
|
||||
* @warning Support for BC (or BCE) years is still experimental. I still need
|
||||
* confirmation on this issue: it looks as if C++ years are astronomical years,
|
||||
* which means they have a Year Zero. Regular BC/AD years do not have a year
|
||||
* zero, so the year 1 AD follows directly after 1 BC.
|
||||
*
|
||||
* So, what to our calendars (and to PostgreSQL) is the year "0001 BC" seems to
|
||||
* count as year "0" in a `std::chrono::year_month_day`. The year 0001 AD is
|
||||
* still equal to 1 as you'd expect, and all AD years work normally, but all
|
||||
* years before then are shifted by one. For instance, the year 543 BC would
|
||||
* be -542 in C++.
|
||||
*/
|
||||
template<> struct PQXX_LIBEXPORT string_traits<std::chrono::year_month_day>
|
||||
{
|
||||
[[nodiscard]] static zview
|
||||
to_buf(char *begin, char *end, std::chrono::year_month_day const &value)
|
||||
{
|
||||
return generic_to_buf(begin, end, value);
|
||||
}
|
||||
|
||||
static char *
|
||||
into_buf(char *begin, char *end, std::chrono::year_month_day const &value);
|
||||
|
||||
[[nodiscard]] static std::chrono::year_month_day
|
||||
from_string(std::string_view text);
|
||||
|
||||
[[nodiscard]] static std::size_t
|
||||
size_buffer(std::chrono::year_month_day const &) noexcept
|
||||
{
|
||||
static_assert(int{(std::chrono::year::min)()} >= -99999);
|
||||
static_assert(int{(std::chrono::year::max)()} <= 99999);
|
||||
return 5 + 1 + 2 + 1 + 2 + std::size(s_bc) + 1;
|
||||
}
|
||||
|
||||
private:
|
||||
/// The "BC" suffix for years before 1 AD.
|
||||
static constexpr std::string_view s_bc{" BC"sv};
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif // PQXX_HAVE_YEAR_MONTH_DAY
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::transaction class.
|
||||
*
|
||||
* pqxx::transaction represents a standard database transaction.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/transaction.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,108 @@
|
||||
/* Definition of the pqxx::transaction class.
|
||||
* pqxx::transaction represents a standard database transaction.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transaction 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_TRANSACTION
|
||||
#define PQXX_H_TRANSACTION
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/dbtransaction.hxx"
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
/// Helper base class for the @ref transaction class template.
|
||||
class PQXX_LIBEXPORT basic_transaction : public dbtransaction
|
||||
{
|
||||
protected:
|
||||
basic_transaction(
|
||||
connection &c, zview begin_command, std::string_view tname);
|
||||
basic_transaction(connection &c, zview begin_command, std::string &&tname);
|
||||
basic_transaction(connection &c, zview begin_command);
|
||||
|
||||
virtual ~basic_transaction() noexcept override = 0;
|
||||
|
||||
private:
|
||||
virtual void do_commit() override;
|
||||
};
|
||||
} // namespace pqxx::internal
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/**
|
||||
* @ingroup transactions
|
||||
*/
|
||||
//@{
|
||||
|
||||
/// Standard back-end transaction, templatised on isolation level.
|
||||
/** This is the type you'll normally want to use to represent a transaction on
|
||||
* the database.
|
||||
*
|
||||
* Usage example: double all wages.
|
||||
*
|
||||
* ```cxx
|
||||
* extern connection C;
|
||||
* work T(C);
|
||||
* try
|
||||
* {
|
||||
* T.exec0("UPDATE employees SET wage=wage*2");
|
||||
* T.commit(); // NOTE: do this inside try block
|
||||
* }
|
||||
* catch (exception const &e)
|
||||
* {
|
||||
* cerr << e.what() << endl;
|
||||
* T.abort(); // Usually not needed; same happens when T's life ends.
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
template<
|
||||
isolation_level ISOLATION = isolation_level::read_committed,
|
||||
write_policy READWRITE = write_policy::read_write>
|
||||
class transaction final : public internal::basic_transaction
|
||||
{
|
||||
public:
|
||||
/// Begin a transaction.
|
||||
/**
|
||||
* @param c Connection for this transaction to operate on.
|
||||
* @param tname Optional name for transaction. Must begin with a letter and
|
||||
* may contain letters and digits only.
|
||||
*/
|
||||
transaction(connection &c, std::string_view tname) :
|
||||
internal::basic_transaction{
|
||||
c, internal::begin_cmd<ISOLATION, READWRITE>, tname}
|
||||
{}
|
||||
|
||||
/// Begin a transaction.
|
||||
/**
|
||||
* @param c Connection for this transaction to operate on.
|
||||
* may contain letters and digits only.
|
||||
*/
|
||||
explicit transaction(connection &c) :
|
||||
internal::basic_transaction{
|
||||
c, internal::begin_cmd<ISOLATION, READWRITE>}
|
||||
{}
|
||||
|
||||
virtual ~transaction() noexcept override { close(); }
|
||||
};
|
||||
|
||||
|
||||
/// The default transaction type.
|
||||
using work = transaction<>;
|
||||
|
||||
/// Read-only transaction.
|
||||
using read_transaction =
|
||||
transaction<isolation_level::read_committed, write_policy::read_only>;
|
||||
|
||||
//@}
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
/** Base for the transaction classes.
|
||||
*
|
||||
* pqxx::transaction_base defines the interface for any abstract class that
|
||||
* represents a database transaction.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/transaction_base.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,810 @@
|
||||
/* Common code and definitions for the transaction classes.
|
||||
*
|
||||
* pqxx::transaction_base defines the interface for any abstract class that
|
||||
* represents a database transaction.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transaction_base 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_TRANSACTION_BASE
|
||||
#define PQXX_H_TRANSACTION_BASE
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
/* End-user programs need not include this file, unless they define their own
|
||||
* transaction classes. This is not something the typical program should want
|
||||
* to do.
|
||||
*
|
||||
* However, reading this file is worthwhile because it defines the public
|
||||
* interface for the available transaction classes such as transaction and
|
||||
* nontransaction.
|
||||
*/
|
||||
|
||||
#include "pqxx/connection.hxx"
|
||||
#include "pqxx/internal/concat.hxx"
|
||||
#include "pqxx/internal/encoding_group.hxx"
|
||||
#include "pqxx/isolation.hxx"
|
||||
#include "pqxx/result.hxx"
|
||||
#include "pqxx/row.hxx"
|
||||
#include "pqxx/stream_from.hxx"
|
||||
#include "pqxx/util.hxx"
|
||||
|
||||
namespace pqxx::internal::gate
|
||||
{
|
||||
class transaction_subtransaction;
|
||||
class transaction_sql_cursor;
|
||||
class transaction_stream_to;
|
||||
class transaction_transaction_focus;
|
||||
} // namespace pqxx::internal::gate
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
|
||||
class transaction_focus;
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup transactions Transaction classes
|
||||
*
|
||||
* All database access goes through instances of these classes.
|
||||
* However, not all implementations of this interface need to provide full
|
||||
* transactional integrity.
|
||||
*
|
||||
* Several implementations of this interface are shipped with libpqxx,
|
||||
* including the plain transaction class, the entirely unprotected
|
||||
* nontransaction, and the more cautious robusttransaction.
|
||||
*/
|
||||
|
||||
/// Interface definition (and common code) for "transaction" classes.
|
||||
/**
|
||||
* @ingroup transactions
|
||||
*
|
||||
* Abstract base class for all transaction types.
|
||||
*/
|
||||
class PQXX_LIBEXPORT PQXX_NOVTABLE transaction_base
|
||||
{
|
||||
public:
|
||||
transaction_base() = delete;
|
||||
transaction_base(transaction_base const &) = delete;
|
||||
transaction_base(transaction_base &&) = delete;
|
||||
transaction_base &operator=(transaction_base const &) = delete;
|
||||
transaction_base &operator=(transaction_base &&) = delete;
|
||||
|
||||
virtual ~transaction_base() = 0;
|
||||
|
||||
/// Commit the transaction.
|
||||
/** Make the effects of this transaction definite. If you destroy a
|
||||
* transaction without invoking its @ref commit() first, that will implicitly
|
||||
* abort it. (For the @ref nontransaction class though, "commit" and "abort"
|
||||
* really don't do anything, hence its name.)
|
||||
*
|
||||
* There is, however, a minute risk that you might lose your connection to
|
||||
* the database at just the wrong moment here. In that case, libpqxx may be
|
||||
* unable to determine whether the database was able to complete the
|
||||
* transaction, or had to roll it back. In that scenario, @ref commit() will
|
||||
* throw an in_doubt_error. There is a different transaction class called
|
||||
* @ref robusttransaction which takes some special precautions to reduce this
|
||||
* risk.
|
||||
*/
|
||||
void commit();
|
||||
|
||||
/// Abort the transaction.
|
||||
/** No special effort is required to call this function; it will be called
|
||||
* implicitly when the transaction is destructed.
|
||||
*/
|
||||
void abort();
|
||||
|
||||
/**
|
||||
* @ingroup escaping-functions
|
||||
*
|
||||
* Use these when writing SQL queries that incorporate C++ values as SQL
|
||||
* constants.
|
||||
*
|
||||
* The functions you see here are just convenience shortcuts to the same
|
||||
* functions on the connection object.
|
||||
*/
|
||||
//@{
|
||||
/// Escape string for use as SQL string literal in this transaction.
|
||||
template<typename... ARGS> [[nodiscard]] auto esc(ARGS &&...args) const
|
||||
{
|
||||
return conn().esc(std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
/// Escape binary data for use as SQL string literal in this transaction.
|
||||
/** Raw, binary data is treated differently from regular strings. Binary
|
||||
* strings are never interpreted as text, so they may safely include byte
|
||||
* values or byte sequences that don't happen to represent valid characters
|
||||
* in the character encoding being used.
|
||||
*
|
||||
* The binary string does not stop at the first zero byte, as is the case
|
||||
* with textual strings. Instead, it may contain zero bytes anywhere. If
|
||||
* it happens to contain bytes that look like quote characters, or other
|
||||
* things that can disrupt their use in SQL queries, they will be replaced
|
||||
* with special escape sequences.
|
||||
*/
|
||||
template<typename... ARGS> [[nodiscard]] auto esc_raw(ARGS &&...args) const
|
||||
{
|
||||
return conn().esc_raw(std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
/// Unescape binary data, e.g. from a table field or notification payload.
|
||||
/** Takes a binary string as escaped by PostgreSQL, and returns a restored
|
||||
* copy of the original binary data.
|
||||
*/
|
||||
[[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string
|
||||
unesc_raw(zview text) const
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return conn().unesc_raw(text);
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
/// Unescape binary data, e.g. from a table field or notification payload.
|
||||
/** Takes a binary string as escaped by PostgreSQL, and returns a restored
|
||||
* copy of the original binary data.
|
||||
*/
|
||||
[[nodiscard]] std::basic_string<std::byte> unesc_bin(zview text)
|
||||
{
|
||||
return conn().unesc_bin(text);
|
||||
}
|
||||
|
||||
/// Unescape binary data, e.g. from a table field or notification payload.
|
||||
/** Takes a binary string as escaped by PostgreSQL, and returns a restored
|
||||
* copy of the original binary data.
|
||||
*/
|
||||
[[nodiscard, deprecated("Use unesc_bin() instead.")]] std::string
|
||||
unesc_raw(char const *text) const
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return conn().unesc_raw(text);
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
/// Unescape binary data, e.g. from a table field or notification payload.
|
||||
/** Takes a binary string as escaped by PostgreSQL, and returns a restored
|
||||
* copy of the original binary data.
|
||||
*/
|
||||
[[nodiscard]] std::basic_string<std::byte> unesc_bin(char const text[])
|
||||
{
|
||||
return conn().unesc_bin(text);
|
||||
}
|
||||
|
||||
/// Represent object as SQL string, including quoting & escaping.
|
||||
/** Nulls are recognized and represented as SQL nulls. */
|
||||
template<typename T> [[nodiscard]] std::string quote(T const &t) const
|
||||
{
|
||||
return conn().quote(t);
|
||||
}
|
||||
|
||||
[[deprecated(
|
||||
"Use std::basic_string<std::byte> instead of binarystring.")]] std::string
|
||||
quote(binarystring const &t) const
|
||||
{
|
||||
return conn().quote(t.bytes_view());
|
||||
}
|
||||
|
||||
/// Binary-escape and quote a binary string for use as an SQL constant.
|
||||
[[deprecated("Use quote(std::basic_string_view<std::byte>).")]] std::string
|
||||
quote_raw(unsigned char const bin[], std::size_t len) const
|
||||
{
|
||||
return quote(binary_cast(bin, len));
|
||||
}
|
||||
|
||||
/// Binary-escape and quote a binary string for use as an SQL constant.
|
||||
[[deprecated("Use quote(std::basic_string_view<std::byte>).")]] std::string
|
||||
quote_raw(zview bin) const;
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
/// Binary-escape and quote a binary string for use as an SQL constant.
|
||||
/** For binary data you can also just use @ref quote(data). */
|
||||
template<binary DATA>
|
||||
[[nodiscard]] std::string quote_raw(DATA const &data) const
|
||||
{
|
||||
return conn().quote_raw(data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Escape an SQL identifier for use in a query.
|
||||
[[nodiscard]] std::string quote_name(std::string_view identifier) const
|
||||
{
|
||||
return conn().quote_name(identifier);
|
||||
}
|
||||
|
||||
/// Escape string for literal LIKE match.
|
||||
[[nodiscard]] std::string
|
||||
esc_like(std::string_view bin, char escape_char = '\\') const
|
||||
{
|
||||
return conn().esc_like(bin, escape_char);
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Command execution
|
||||
*
|
||||
* There are many functions for executing (or "performing") a command (or
|
||||
* "query"). This is the most fundamental thing you can do with the library,
|
||||
* and you always do it from a transaction class.
|
||||
*
|
||||
* Command execution can throw many types of exception, including sql_error,
|
||||
* broken_connection, and many sql_error subtypes such as
|
||||
* feature_not_supported or insufficient_privilege. But any exception thrown
|
||||
* by the C++ standard library may also occur here. All exceptions you will
|
||||
* see libpqxx throw are derived from std::exception.
|
||||
*
|
||||
* One unusual feature in libpqxx is that you can give your query a name or
|
||||
* description. This does not mean anything to the database, but sometimes
|
||||
* it can help libpqxx produce more helpful error messages, making problems
|
||||
* in your code easier to debug.
|
||||
*
|
||||
* Many of the execution functions used to accept a `desc` argument, a
|
||||
* human-readable description of the statement for use in error messages.
|
||||
* This could make failures easier to debug. Future versions will use
|
||||
* C++20's `std::source_location` to identify the failing statement.
|
||||
*/
|
||||
//@{
|
||||
|
||||
/// Execute a command.
|
||||
/**
|
||||
* @param query Query or command to execute.
|
||||
* @param desc Optional identifier for query, to help pinpoint SQL errors.
|
||||
* @return A result set describing the query's or command's result.
|
||||
*/
|
||||
[[deprecated("The desc parameter is going away.")]] result
|
||||
exec(std::string_view query, std::string_view desc);
|
||||
|
||||
/// Execute a command.
|
||||
/**
|
||||
* @param query Query or command to execute.
|
||||
* @return A result set describing the query's or command's result.
|
||||
*/
|
||||
result exec(std::string_view query)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return exec(query, std::string_view{});
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
/// Execute a command.
|
||||
/**
|
||||
* @param query Query or command to execute.
|
||||
* @param desc Optional identifier for query, to help pinpoint SQL errors.
|
||||
* @return A result set describing the query's or command's result.
|
||||
*/
|
||||
[[deprecated(
|
||||
"Pass your query as a std::string_view, not stringstream.")]] result
|
||||
exec(std::stringstream const &query, std::string_view desc)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return exec(query.str(), desc);
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
/// Execute command, which should return zero rows of data.
|
||||
/** Works like @ref exec, but fails if the result contains data. It still
|
||||
* returns a result, however, which may contain useful metadata.
|
||||
*
|
||||
* @throw unexpected_rows If the query returned the wrong number of rows.
|
||||
*/
|
||||
[[deprecated("The desc parameter is going away.")]] result
|
||||
exec0(zview query, std::string_view desc)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return exec_n(0, query, desc);
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
/// Execute command, which should return zero rows of data.
|
||||
/** Works like @ref exec, but fails if the result contains data. It still
|
||||
* returns a result, however, which may contain useful metadata.
|
||||
*
|
||||
* @throw unexpected_rows If the query returned the wrong number of rows.
|
||||
*/
|
||||
result exec0(zview query) { return exec_n(0, query); }
|
||||
|
||||
/// Execute command returning a single row of data.
|
||||
/** Works like @ref exec, but requires the result to contain exactly one row.
|
||||
* The row can be addressed directly, without the need to find the first row
|
||||
* in a result set.
|
||||
*
|
||||
* @throw unexpected_rows If the query returned the wrong number of rows.
|
||||
*/
|
||||
[[deprecated("The desc parameter is going away.")]] row
|
||||
exec1(zview query, std::string_view desc)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return exec_n(1, query, desc).front();
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
/// Execute command returning a single row of data.
|
||||
/** Works like @ref exec, but requires the result to contain exactly one row.
|
||||
* The row can be addressed directly, without the need to find the first row
|
||||
* in a result set.
|
||||
*
|
||||
* @throw unexpected_rows If the query returned the wrong number of rows.
|
||||
*/
|
||||
row exec1(zview query) { return exec_n(1, query).front(); }
|
||||
|
||||
/// Execute command, expect given number of rows.
|
||||
/** Works like @ref exec, but checks that the result has exactly the expected
|
||||
* number of rows.
|
||||
*
|
||||
* @throw unexpected_rows If the query returned the wrong number of rows.
|
||||
*/
|
||||
[[deprecated("The desc parameter is going away.")]] result
|
||||
exec_n(result::size_type rows, zview query, std::string_view desc);
|
||||
|
||||
/// Execute command, expect given number of rows.
|
||||
/** Works like @ref exec, but checks that the result has exactly the expected
|
||||
* number of rows.
|
||||
*
|
||||
* @throw unexpected_rows If the query returned the wrong number of rows.
|
||||
*/
|
||||
result exec_n(result::size_type rows, zview query)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
return exec_n(rows, query, std::string_view{});
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
}
|
||||
|
||||
/// Perform query, expecting exactly 1 row with 1 field, and convert it.
|
||||
/** This is convenience shorthand for querying exactly one value from the
|
||||
* database. It returns that value, converted to the type you specify.
|
||||
*/
|
||||
template<typename TYPE>
|
||||
[[deprecated("The desc parameter is going away.")]] TYPE
|
||||
query_value(zview query, std::string_view desc)
|
||||
{
|
||||
#include "pqxx/internal/ignore-deprecated-pre.hxx"
|
||||
row const r{exec1(query, desc)};
|
||||
#include "pqxx/internal/ignore-deprecated-post.hxx"
|
||||
if (std::size(r) != 1)
|
||||
throw usage_error{internal::concat(
|
||||
"Queried single value from result with ", std::size(r), " columns.")};
|
||||
return r[0].as<TYPE>();
|
||||
}
|
||||
|
||||
/// Perform query, expecting exactly 1 row with 1 field, and convert it.
|
||||
/** This is convenience shorthand for querying exactly one value from the
|
||||
* database. It returns that value, converted to the type you specify.
|
||||
*/
|
||||
template<typename TYPE> TYPE query_value(zview query)
|
||||
{
|
||||
row const r{exec1(query)};
|
||||
if (std::size(r) != 1)
|
||||
throw usage_error{internal::concat(
|
||||
"Queried single value from result with ", std::size(r), " columns.")};
|
||||
return r[0].as<TYPE>();
|
||||
}
|
||||
|
||||
/// Execute a query, and loop over the results row by row.
|
||||
/** Converts the rows to `std::tuple`, of the column types you specify.
|
||||
*
|
||||
* Use this with a range-based "for" loop. It executes the query, and
|
||||
* directly maps the resulting rows onto a `std::tuple` of the types you
|
||||
* specify. It starts before all the data from the server is in, so if your
|
||||
* network connection to the server breaks while you're iterating, you'll get
|
||||
* an exception partway through.
|
||||
*
|
||||
* The stream lives entirely within the lifetime of the transaction. Make
|
||||
* sure you destroy the stream before you destroy the transaction. Either
|
||||
* iterate the stream all the way to the end, or destroy first the stream
|
||||
* and then the transaction without touching either in any other way. Until
|
||||
* the stream has finished, the transaction is in a special state where it
|
||||
* cannot execute queries.
|
||||
*
|
||||
* As a special case, tuple may contain `std::string_view` fields, but the
|
||||
* strings to which they point will only remain valid until you extract the
|
||||
* next row. After that, the memory holding the string may be overwritten or
|
||||
* deallocated.
|
||||
*
|
||||
* If any of the columns can be null, and the C++ type to which it translates
|
||||
* does not have a null value, wrap the type in `std::optional` (or if
|
||||
* you prefer, `std::shared_ptr` or `std::unique_ptr)`. These templates
|
||||
* do recognise null values, and libpqxx will know how to convert to them.
|
||||
*
|
||||
* The connection is in a special state until the iteration finishes. So if
|
||||
* it does not finish due to a `break` or a `return` or an exception, then
|
||||
* the entire connection becomes effectively unusable.
|
||||
*
|
||||
* Querying in this way is faster than the `exec()` methods for larger
|
||||
* results (but slower for small ones). You can start processing rows before
|
||||
* the full result is in. Also, `stream()` scales better in terms of memory
|
||||
* usage. Where @ref exec() reads the entire result into memory at once,
|
||||
* `stream()` will read and process one row at at a time.
|
||||
*
|
||||
* Your query executes as part of a COPY command, not as a stand-alone query,
|
||||
* so there are limitations to what you can do in the query. It can be
|
||||
* either a SELECT or VALUES query; or an INSERT, UPDATE, or DELETE with a
|
||||
* RETURNING clause. See the documentation for PostgreSQL's COPY command for
|
||||
* the details:
|
||||
*
|
||||
* https://www.postgresql.org/docs/current/sql-copy.html
|
||||
*
|
||||
* Iterating in this way does require each of the field types you pass to be
|
||||
* default-constructible, copy-constructible, and assignable. These
|
||||
* requirements may be loosened once libpqxx moves on to C++20.
|
||||
*/
|
||||
template<typename... TYPE>
|
||||
[[nodiscard]] auto stream(std::string_view query) &
|
||||
{
|
||||
// Tricky: std::make_unique() supports constructors but not RVO functions.
|
||||
return pqxx::internal::owning_stream_input_iteration<TYPE...>{
|
||||
std::unique_ptr<stream_from>{
|
||||
new stream_from{stream_from::query(*this, query)}}};
|
||||
}
|
||||
|
||||
// C++20: Concept like std::invocable, but without specifying param types.
|
||||
/// Perform a streaming query, and for each result row, call `func`.
|
||||
/** Here, `func` can be a function, a `std::function`, a lambda, or an
|
||||
* object that supports the function call operator. Of course `func` must
|
||||
* have an unambiguous signature; it can't be overloaded or generic.
|
||||
*
|
||||
* The `for_each` function executes `query` in a stream using
|
||||
* @ref pqxx::stream_from. Every time a row of data comes in from the
|
||||
* server, it converts the row's fields to the types of `func`'s respective
|
||||
* parameters, and calls `func` with those values.
|
||||
*
|
||||
* This will not work for all queries, but straightforward `SELECT` and
|
||||
* `UPDATE ... RETURNING` queries should work. Consult the documentation for
|
||||
* @ref pqxx::stream_from and PostgreSQL's underlying `COPY` command for the
|
||||
* full details.
|
||||
*
|
||||
* Streaming a query like this is likely to be slower than the @ref exec()
|
||||
* functions for small result sets, but faster for large result sets. So if
|
||||
* performance matters, you'll want to use `for_each` if you query large
|
||||
* amounts of data, but not if you do lots of queries with small outputs.
|
||||
*/
|
||||
template<typename CALLABLE>
|
||||
inline auto for_each(std::string_view query, CALLABLE &&func)
|
||||
{
|
||||
using param_types =
|
||||
pqxx::internal::strip_types_t<pqxx::internal::args_t<CALLABLE>>;
|
||||
param_types const *const sample{nullptr};
|
||||
auto data_stream{stream_like(query, sample)};
|
||||
for (auto const &fields : data_stream) std::apply(func, fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Parameterized statements
|
||||
*
|
||||
* You'll often need parameters in the queries you execute: "select the
|
||||
* car with this licence plate." If the parameter is a string, you need to
|
||||
* quote it and escape any special characters inside it, or it may become a
|
||||
* target for an SQL injection attack. If it's an integer (for example),
|
||||
* you need to convert it to a string, but in the database's format, without
|
||||
* locale-specific niceties like "," separators between the thousands.
|
||||
*
|
||||
* Parameterised statements are an easier and safer way to do this. They're
|
||||
* like prepared statements, but for a single use. You don't need to name
|
||||
* them, and you don't need to prepare them first.
|
||||
*
|
||||
* Your query will include placeholders like `$1` and `$2` etc. in the places
|
||||
* where you want the arguments to go. Then, you pass the argument values
|
||||
* and the actual query is constructed for you.
|
||||
*
|
||||
* Pass the exact right number of parameters, and in the right order. The
|
||||
* parameters in the query don't have to be neatly ordered from `$1` to
|
||||
* `$2` to `$3` - but you must pass the argument for `$1` first, the one
|
||||
* for `$2` second, etc.
|
||||
*
|
||||
* @warning Beware of "nul" bytes. Any string you pass as a parameter will
|
||||
* end at the first char with value zero. If you pass a string that contains
|
||||
* a zero byte, the last byte in the value will be the one just before the
|
||||
* zero.
|
||||
*/
|
||||
//@{
|
||||
/// Execute an SQL statement with parameters.
|
||||
template<typename... Args> result exec_params(zview query, Args &&...args)
|
||||
{
|
||||
params pp(args...);
|
||||
return internal_exec_params(query, pp.make_c_params());
|
||||
}
|
||||
|
||||
// Execute parameterised statement, expect a single-row result.
|
||||
/** @throw unexpected_rows if the result does not consist of exactly one row.
|
||||
*/
|
||||
template<typename... Args> row exec_params1(zview query, Args &&...args)
|
||||
{
|
||||
return exec_params_n(1, query, std::forward<Args>(args)...).front();
|
||||
}
|
||||
|
||||
// Execute parameterised statement, expect a result with zero rows.
|
||||
/** @throw unexpected_rows if the result contains rows.
|
||||
*/
|
||||
template<typename... Args> result exec_params0(zview query, Args &&...args)
|
||||
{
|
||||
return exec_params_n(0, query, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Execute parameterised statement, expect exactly a given number of rows.
|
||||
/** @throw unexpected_rows if the result contains the wrong number of rows.
|
||||
*/
|
||||
template<typename... Args>
|
||||
result exec_params_n(std::size_t rows, zview query, Args &&...args)
|
||||
{
|
||||
auto const r{exec_params(query, std::forward<Args>(args)...)};
|
||||
check_rowcount_params(rows, std::size(r));
|
||||
return r;
|
||||
}
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Prepared statements
|
||||
*
|
||||
* These are very similar to parameterised statements. The difference is
|
||||
* that you prepare them in advance, giving them identifying names. You can
|
||||
* then call them by these names, passing in the argument values appropriate
|
||||
* for that call.
|
||||
*
|
||||
* You prepare a statement on the connection, using
|
||||
* @ref pqxx::connection::prepare(). But you then call the statement in a
|
||||
* transaction, using the functions you see here.
|
||||
*
|
||||
* Never try to prepare, execute, or unprepare a prepared statement manually
|
||||
* using direct SQL queries when you also use the libpqxx equivalents. For
|
||||
* any given statement, either prepare, manage, and execute it through the
|
||||
* dedicated libpqxx functions; or do it all directly in SQL. Don't mix the
|
||||
* two, or the code may get confused.
|
||||
*
|
||||
* See \ref prepared for a full discussion.
|
||||
*
|
||||
* @warning Beware of "nul" bytes. Any string you pass as a parameter will
|
||||
* end at the first char with value zero. If you pass a string that contains
|
||||
* a zero byte, the last byte in the value will be the one just before the
|
||||
* zero. If you need a zero byte, you're dealing with binary strings, not
|
||||
* regular strings. Represent binary strings on the SQL side as `BYTEA`
|
||||
* (or as large objects). On the C++ side, use types like
|
||||
* `std::basic_string<std::byte>` or `std::basic_string_view<std::byte>`
|
||||
* or (in C++20) `std::vector<std::byte>`. Also, consider large objects on
|
||||
* the SQL side and @ref blob on the C++ side.
|
||||
*/
|
||||
//@{
|
||||
|
||||
/// Execute a prepared statement, with optional arguments.
|
||||
template<typename... Args>
|
||||
result exec_prepared(zview statement, Args &&...args)
|
||||
{
|
||||
params pp(args...);
|
||||
return internal_exec_prepared(statement, pp.make_c_params());
|
||||
}
|
||||
|
||||
/// Execute a prepared statement, and expect a single-row result.
|
||||
/** @throw pqxx::unexpected_rows if the result was not exactly 1 row.
|
||||
*/
|
||||
template<typename... Args>
|
||||
row exec_prepared1(zview statement, Args &&...args)
|
||||
{
|
||||
return exec_prepared_n(1, statement, std::forward<Args>(args)...).front();
|
||||
}
|
||||
|
||||
/// Execute a prepared statement, and expect a result with zero rows.
|
||||
/** @throw pqxx::unexpected_rows if the result contained rows.
|
||||
*/
|
||||
template<typename... Args>
|
||||
result exec_prepared0(zview statement, Args &&...args)
|
||||
{
|
||||
return exec_prepared_n(0, statement, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Execute a prepared statement, expect a result with given number of rows.
|
||||
/** @throw pqxx::unexpected_rows if the result did not contain exactly the
|
||||
* given number of rows.
|
||||
*/
|
||||
template<typename... Args>
|
||||
result
|
||||
exec_prepared_n(result::size_type rows, zview statement, Args &&...args)
|
||||
{
|
||||
auto const r{exec_prepared(statement, std::forward<Args>(args)...)};
|
||||
check_rowcount_prepared(statement, rows, std::size(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
//@}
|
||||
|
||||
/**
|
||||
* @name Error/warning output
|
||||
*/
|
||||
//@{
|
||||
/// Have connection process a warning message.
|
||||
void process_notice(char const msg[]) const { m_conn.process_notice(msg); }
|
||||
/// Have connection process a warning message.
|
||||
void process_notice(zview msg) const { m_conn.process_notice(msg); }
|
||||
//@}
|
||||
|
||||
/// The connection in which this transaction lives.
|
||||
[[nodiscard]] constexpr connection &conn() const noexcept { return m_conn; }
|
||||
|
||||
/// Set session variable using SQL "SET" command.
|
||||
/** @deprecated To set a transaction-local variable, execute an SQL `SET`
|
||||
* command. To set a session variable, use the connection's
|
||||
* @ref set_session_var function.
|
||||
*
|
||||
* @warning When setting a string value, you must make sure that the string
|
||||
* is "safe." If you call @ref quote() on the string, it will return a
|
||||
* safely escaped and quoted version for use as an SQL literal.
|
||||
*
|
||||
* @warning This function executes SQL. Do not try to set or get variables
|
||||
* while a pipeline or table stream is active.
|
||||
*
|
||||
* @param var The variable to set.
|
||||
* @param value The new value to store in the variable. This can be any SQL
|
||||
* expression.
|
||||
*/
|
||||
[[deprecated(
|
||||
"Set transaction-local variables using SQL SET statements.")]] void
|
||||
set_variable(std::string_view var, std::string_view value);
|
||||
|
||||
/// Read session variable using SQL "SHOW" command.
|
||||
/** @warning This executes SQL. Do not try to set or get variables while a
|
||||
* pipeline or table stream is active.
|
||||
*/
|
||||
[[deprecated("Read variables using SQL SHOW statements.")]] std::string
|
||||
get_variable(std::string_view);
|
||||
|
||||
// C++20: constexpr.
|
||||
/// Transaction name, if you passed one to the constructor; or empty string.
|
||||
[[nodiscard]] std::string_view name() const &noexcept { return m_name; }
|
||||
|
||||
protected:
|
||||
/// Create a transaction (to be called by implementation classes only).
|
||||
/** The name, if nonempty, must begin with a letter and may contain letters
|
||||
* and digits only.
|
||||
*/
|
||||
transaction_base(
|
||||
connection &c, std::string_view tname,
|
||||
std::shared_ptr<std::string> rollback_cmd) :
|
||||
m_conn{c}, m_name{tname}, m_rollback_cmd{rollback_cmd}
|
||||
{}
|
||||
|
||||
/// Create a transaction (to be called by implementation classes only).
|
||||
/** Its rollback command will be "ROLLBACK".
|
||||
*
|
||||
* The name, if nonempty, must begin with a letter and may contain letters
|
||||
* and digits only.
|
||||
*/
|
||||
transaction_base(connection &c, std::string_view tname);
|
||||
|
||||
/// Create a transaction (to be called by implementation classes only).
|
||||
explicit transaction_base(connection &c);
|
||||
|
||||
/// Register this transaction with the connection.
|
||||
void register_transaction();
|
||||
|
||||
/// End transaction. To be called by implementing class' destructor.
|
||||
void close() noexcept;
|
||||
|
||||
/// To be implemented by derived implementation class: commit transaction.
|
||||
virtual void do_commit() = 0;
|
||||
|
||||
/// Transaction type-specific way of aborting a transaction.
|
||||
/** @warning This will become "final", since this function can be called
|
||||
* from the implementing class destructor.
|
||||
*/
|
||||
virtual void do_abort();
|
||||
|
||||
/// Set the rollback command.
|
||||
void set_rollback_cmd(std::shared_ptr<std::string> cmd)
|
||||
{
|
||||
m_rollback_cmd = cmd;
|
||||
}
|
||||
|
||||
/// Execute query on connection directly.
|
||||
result direct_exec(std::string_view, std::string_view desc = ""sv);
|
||||
result
|
||||
direct_exec(std::shared_ptr<std::string>, std::string_view desc = ""sv);
|
||||
|
||||
private:
|
||||
enum class status
|
||||
{
|
||||
active,
|
||||
aborted,
|
||||
committed,
|
||||
in_doubt
|
||||
};
|
||||
|
||||
PQXX_PRIVATE void check_pending_error();
|
||||
|
||||
result
|
||||
internal_exec_prepared(zview statement, internal::c_params const &args);
|
||||
|
||||
result internal_exec_params(zview query, internal::c_params const &args);
|
||||
|
||||
/// Throw unexpected_rows if prepared statement returned wrong no. of rows.
|
||||
void check_rowcount_prepared(
|
||||
zview statement, result::size_type expected_rows,
|
||||
result::size_type actual_rows);
|
||||
|
||||
/// Throw unexpected_rows if wrong row count from parameterised statement.
|
||||
void
|
||||
check_rowcount_params(std::size_t expected_rows, std::size_t actual_rows);
|
||||
|
||||
/// Describe this transaction to humans, e.g. "transaction 'foo'".
|
||||
[[nodiscard]] std::string description() const;
|
||||
|
||||
friend class pqxx::internal::gate::transaction_transaction_focus;
|
||||
PQXX_PRIVATE void register_focus(transaction_focus *);
|
||||
PQXX_PRIVATE void unregister_focus(transaction_focus *) noexcept;
|
||||
PQXX_PRIVATE void register_pending_error(zview) noexcept;
|
||||
PQXX_PRIVATE void register_pending_error(std::string &&) noexcept;
|
||||
|
||||
/// Like @ref stream(), but takes a tuple rather than a parameter pack.
|
||||
template<typename... ARGS>
|
||||
auto stream_like(std::string_view query, std::tuple<ARGS...> const *)
|
||||
{
|
||||
return stream<ARGS...>(query);
|
||||
}
|
||||
|
||||
connection &m_conn;
|
||||
|
||||
/// Current "focus": a pipeline, a nested transaction, a stream...
|
||||
/** This pointer is used for only one purpose: sanity checks against mistakes
|
||||
* such as opening one while another is still active.
|
||||
*/
|
||||
transaction_focus const *m_focus = nullptr;
|
||||
|
||||
status m_status = status::active;
|
||||
bool m_registered = false;
|
||||
std::string m_name;
|
||||
std::string m_pending_error;
|
||||
|
||||
/// SQL command for aborting this type of transaction.
|
||||
std::shared_ptr<std::string> m_rollback_cmd;
|
||||
|
||||
static constexpr std::string_view s_type_name{"transaction"sv};
|
||||
};
|
||||
|
||||
|
||||
// C++20: Can borrowed_range help?
|
||||
/// Forbidden specialisation: underlying buffer immediately goes out of scope.
|
||||
template<>
|
||||
std::string_view transaction_base::query_value<std::string_view>(
|
||||
zview query, std::string_view desc) = delete;
|
||||
/// Forbidden specialisation: underlying buffer immediately goes out of scope.
|
||||
template<>
|
||||
zview transaction_base::query_value<zview>(
|
||||
zview query, std::string_view desc) = delete;
|
||||
|
||||
} // namespace pqxx
|
||||
|
||||
|
||||
namespace pqxx::internal
|
||||
{
|
||||
/// The SQL command for starting a given type of transaction.
|
||||
template<pqxx::isolation_level isolation, pqxx::write_policy rw>
|
||||
extern const zview begin_cmd;
|
||||
|
||||
// These are not static members, so "constexpr" does not imply "inline".
|
||||
template<>
|
||||
inline constexpr zview begin_cmd<read_committed, write_policy::read_write>{
|
||||
"BEGIN"_zv};
|
||||
template<>
|
||||
inline constexpr zview begin_cmd<read_committed, write_policy::read_only>{
|
||||
"BEGIN READ ONLY"_zv};
|
||||
template<>
|
||||
inline constexpr zview begin_cmd<repeatable_read, write_policy::read_write>{
|
||||
"BEGIN ISOLATION LEVEL REPEATABLE READ"_zv};
|
||||
template<>
|
||||
inline constexpr zview begin_cmd<repeatable_read, write_policy::read_only>{
|
||||
"BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY"_zv};
|
||||
template<>
|
||||
inline constexpr zview begin_cmd<serializable, write_policy::read_write>{
|
||||
"BEGIN ISOLATION LEVEL SERIALIZABLE"_zv};
|
||||
template<>
|
||||
inline constexpr zview begin_cmd<serializable, write_policy::read_only>{
|
||||
"BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY"_zv};
|
||||
} // namespace pqxx::internal
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Transaction focus: types which monopolise a transaction's attention.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/types.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,89 @@
|
||||
/** Transaction focus: types which monopolise a transaction's attention.
|
||||
*
|
||||
* 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_TRANSACTION_FOCUS
|
||||
#define PQXX_H_TRANSACTION_FOCUS
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include "pqxx/util.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Base class for things that monopolise a transaction's attention.
|
||||
/** You probably won't need to use this class. But it can be useful to _know_
|
||||
* that a given libpqxx class is derived from it.
|
||||
*
|
||||
* Pipelines, SQL statements, and data streams are examples of classes derived
|
||||
* from `transaction_focus`. For any given transaction, only one object of
|
||||
* such a class can be active at any given time.
|
||||
*/
|
||||
class PQXX_LIBEXPORT transaction_focus
|
||||
{
|
||||
public:
|
||||
transaction_focus(
|
||||
transaction_base &t, std::string_view cname, std::string_view oname) :
|
||||
m_trans{t}, m_classname{cname}, m_name{oname}
|
||||
{}
|
||||
|
||||
transaction_focus(
|
||||
transaction_base &t, std::string_view cname, std::string &&oname) :
|
||||
m_trans{t}, m_classname{cname}, m_name{std::move(oname)}
|
||||
{}
|
||||
|
||||
transaction_focus(transaction_base &t, std::string_view cname) :
|
||||
m_trans{t}, m_classname{cname}
|
||||
{}
|
||||
|
||||
transaction_focus() = delete;
|
||||
transaction_focus(transaction_focus const &) = delete;
|
||||
transaction_focus &operator=(transaction_focus const &) = delete;
|
||||
|
||||
/// Class name, for human consumption.
|
||||
[[nodiscard]] constexpr std::string_view classname() const noexcept
|
||||
{
|
||||
return m_classname;
|
||||
}
|
||||
|
||||
/// Name for this object, if the caller passed one; empty string otherwise.
|
||||
[[nodiscard]] std::string_view name() const &noexcept { return m_name; }
|
||||
|
||||
[[nodiscard]] std::string description() const
|
||||
{
|
||||
return pqxx::internal::describe_object(m_classname, m_name);
|
||||
}
|
||||
|
||||
/// Can't move a transaction_focus.
|
||||
/** Moving the transaction_focus would break the transaction's reference back
|
||||
* to the object.
|
||||
*/
|
||||
transaction_focus(transaction_focus &&) = delete;
|
||||
|
||||
/// Can't move a transaction_focus.
|
||||
/** Moving the transaction_focus would break the transaction's reference back
|
||||
* to the object.
|
||||
*/
|
||||
transaction_focus &operator=(transaction_focus &&) = delete;
|
||||
|
||||
protected:
|
||||
void register_me();
|
||||
void unregister_me() noexcept;
|
||||
void reg_pending_error(std::string const &) noexcept;
|
||||
bool registered() const noexcept { return m_registered; }
|
||||
|
||||
transaction_base &m_trans;
|
||||
|
||||
private:
|
||||
bool m_registered = false;
|
||||
std::string_view m_classname;
|
||||
std::string m_name;
|
||||
};
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
/** pqxx::transactor class.
|
||||
*
|
||||
* pqxx::transactor is a framework-style wrapper for safe transactions.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/transactor.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,147 @@
|
||||
/* Transactor framework, a wrapper for safely retryable transactions.
|
||||
*
|
||||
* DO NOT INCLUDE THIS FILE DIRECTLY; include pqxx/transactor 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_TRANSACTOR
|
||||
#define PQXX_H_TRANSACTOR
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
#include "pqxx/connection.hxx"
|
||||
#include "pqxx/transaction.hxx"
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/**
|
||||
* @defgroup transactor Transactor framework
|
||||
*
|
||||
* Sometimes a transaction can fail for completely transient reasons, such as a
|
||||
* conflict with another transaction in SERIALIZABLE isolation. The right way
|
||||
* to handle those failures is often just to re-run the transaction from
|
||||
* scratch.
|
||||
*
|
||||
* For example, your REST API might be handling each HTTP request in its own
|
||||
* database transaction, and if this kind of transient failure happens, you
|
||||
* simply want to "replay" the whole request, in a fresh transaction.
|
||||
*
|
||||
* You won't necessarily want to execute the exact same SQL commands with the
|
||||
* exact same data. Some of your SQL statements may depend on state that can
|
||||
* vary between retries. Data in the database may already have changed, for
|
||||
* instance. So instead of dumbly replaying the SQL, you re-run the same
|
||||
* application code that produced those SQL commands, from the start.
|
||||
*
|
||||
* The transactor framework makes it a little easier for you to do this safely,
|
||||
* and avoid typical pitfalls. You encapsulate the work that you want to do
|
||||
* into a callable that you pass to the @ref perform function.
|
||||
*
|
||||
* Here's how it works. You write your transaction code as a lambda or
|
||||
* function, which creates its own transaction object, does its work, and
|
||||
* commits at the end. You pass that callback to @ref pqxx::perform, which
|
||||
* runs it for you.
|
||||
*
|
||||
* If there's a failure inside your callback, there will be an exception. Your
|
||||
* transaction object goes out of scope and gets destroyed, so that it aborts
|
||||
* implicitly. Seeing this, @ref perform tries running your callback again. It
|
||||
* stops doing that when the callback succeeds, or when it has failed too many
|
||||
* times, or when there's an error that leaves the database in an unknown
|
||||
* state, such as a lost connection just while we're waiting for the database
|
||||
* to confirm a commit. It all depends on the type of exception.
|
||||
*
|
||||
* The callback takes no arguments. If you're using lambdas, the easy way to
|
||||
* pass arguments is for the lambda to "capture" them from your variables. Or,
|
||||
* if you're using functions, you may want to use `std::bind`.
|
||||
*
|
||||
* Once your callback succeeds, it can return a result, and @ref perform will
|
||||
* return that result back to you.
|
||||
*/
|
||||
//@{
|
||||
|
||||
/// Simple way to execute a transaction with automatic retry.
|
||||
/**
|
||||
* Executes your transaction code as a callback. Repeats it until it completes
|
||||
* normally, or it throws an error other than the few libpqxx-generated
|
||||
* exceptions that the framework understands, or after a given number of failed
|
||||
* attempts, or if the transaction ends in an "in-doubt" state.
|
||||
*
|
||||
* (An in-doubt state is one where libpqxx cannot determine whether the server
|
||||
* finally committed a transaction or not. This can happen if the network
|
||||
* connection to the server is lost just while we're waiting for its reply to
|
||||
* a "commit" statement. The server may have completed the commit, or not, but
|
||||
* it can't tell you because there's no longer a connection.
|
||||
*
|
||||
* Using this still takes a bit of care. If your callback makes use of data
|
||||
* from the database, you'll probably have to query that data within your
|
||||
* callback. If the attempt to perform your callback fails, and the framework
|
||||
* tries again, you'll be in a new transaction and the data in the database may
|
||||
* have changed under your feet.
|
||||
*
|
||||
* Also be careful about changing variables or data structures from within
|
||||
* your callback. The run may still fail, and perhaps get run again. The
|
||||
* ideal way to do it (in most cases) is to return your result from your
|
||||
* callback, and change your program's data state only after @ref perform
|
||||
* completes successfully.
|
||||
*
|
||||
* @param callback Transaction code that can be called with no arguments.
|
||||
* @param attempts Maximum number of times to attempt performing callback.
|
||||
* Must be greater than zero.
|
||||
* @return Whatever your callback returns.
|
||||
*/
|
||||
template<typename TRANSACTION_CALLBACK>
|
||||
inline auto perform(TRANSACTION_CALLBACK &&callback, int attempts = 3)
|
||||
-> std::invoke_result_t<TRANSACTION_CALLBACK>
|
||||
{
|
||||
if (attempts <= 0)
|
||||
throw std::invalid_argument{
|
||||
"Zero or negative number of attempts passed to pqxx::perform()."};
|
||||
|
||||
for (; attempts > 0; --attempts)
|
||||
{
|
||||
try
|
||||
{
|
||||
return std::invoke(callback);
|
||||
}
|
||||
catch (in_doubt_error const &)
|
||||
{
|
||||
// Not sure whether transaction went through or not. The last thing in
|
||||
// the world that we should do now is try again!
|
||||
throw;
|
||||
}
|
||||
catch (statement_completion_unknown const &)
|
||||
{
|
||||
// Not sure whether our last statement succeeded. Don't risk running it
|
||||
// again.
|
||||
throw;
|
||||
}
|
||||
catch (broken_connection const &)
|
||||
{
|
||||
// Connection failed. May be worth retrying, if the transactor opens its
|
||||
// own connection.
|
||||
if (attempts <= 1)
|
||||
throw;
|
||||
continue;
|
||||
}
|
||||
catch (transaction_rollback const &)
|
||||
{
|
||||
// Some error that may well be transient, such as serialization failure
|
||||
// or deadlock. Worth retrying.
|
||||
if (attempts <= 1)
|
||||
throw;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw pqxx::internal_error{"No outcome reached on perform()."};
|
||||
}
|
||||
} // namespace pqxx
|
||||
//@}
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Basic typedefs and forward declarations.
|
||||
*/
|
||||
// Actual definitions in .hxx file so editors and such recognize file type.
|
||||
#include "pqxx/internal/header-pre.hxx"
|
||||
#include "pqxx/types.hxx"
|
||||
#include "pqxx/internal/header-post.hxx"
|
||||
@@ -0,0 +1,173 @@
|
||||
/* Basic type aliases and forward declarations.
|
||||
*
|
||||
* 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_TYPES
|
||||
#define PQXX_H_TYPES
|
||||
|
||||
#if !defined(PQXX_HEADER_PRE)
|
||||
# error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
|
||||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS) && __has_include(<ranges>)
|
||||
# include <ranges>
|
||||
#endif
|
||||
|
||||
|
||||
namespace pqxx
|
||||
{
|
||||
/// Number of rows in a result set.
|
||||
using result_size_type = int;
|
||||
|
||||
/// Difference between result sizes.
|
||||
using result_difference_type = int;
|
||||
|
||||
/// Number of fields in a row of database data.
|
||||
using row_size_type = int;
|
||||
|
||||
/// Difference between row sizes.
|
||||
using row_difference_type = int;
|
||||
|
||||
/// Number of bytes in a field of database data.
|
||||
using field_size_type = std::size_t;
|
||||
|
||||
/// Number of bytes in a large object.
|
||||
using large_object_size_type = int64_t;
|
||||
|
||||
|
||||
// Forward declarations, to help break compilation dependencies.
|
||||
// These won't necessarily include all classes in libpqxx.
|
||||
class binarystring;
|
||||
class connection;
|
||||
class const_result_iterator;
|
||||
class const_reverse_result_iterator;
|
||||
class const_reverse_row_iterator;
|
||||
class const_row_iterator;
|
||||
class dbtransaction;
|
||||
class field;
|
||||
class largeobjectaccess;
|
||||
class notification_receiver;
|
||||
struct range_error;
|
||||
class result;
|
||||
class row;
|
||||
class stream_from;
|
||||
class transaction_base;
|
||||
|
||||
/// Marker for @ref stream_from constructors: "stream from table."
|
||||
/** @deprecated Use @ref stream_from::table() instead.
|
||||
*/
|
||||
struct from_table_t
|
||||
{};
|
||||
|
||||
/// Marker for @ref stream_from constructors: "stream from query."
|
||||
/** @deprecated Use @ref stream_from::query() instead.
|
||||
*/
|
||||
struct from_query_t
|
||||
{};
|
||||
|
||||
|
||||
/// Format code: is data text or binary?
|
||||
/** Binary-compatible with libpq's format codes.
|
||||
*/
|
||||
enum class format : int
|
||||
{
|
||||
text = 0,
|
||||
binary = 1,
|
||||
};
|
||||
|
||||
|
||||
/// Remove any constness, volatile, and reference-ness from a type.
|
||||
/** @deprecated In C++20 we'll replace this with std::remove_cvref.
|
||||
*/
|
||||
template<typename TYPE>
|
||||
using strip_t = std::remove_cv_t<std::remove_reference_t<TYPE>>;
|
||||
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
/// The type of a container's elements.
|
||||
/** At the time of writing there's a similar thing in `std::experimental`,
|
||||
* which we may or may not end up using for this.
|
||||
*/
|
||||
template<std::ranges::range CONTAINER>
|
||||
using value_type = strip_t<decltype(*std::begin(std::declval<CONTAINER>()))>;
|
||||
#else // PQXX_HAVE_CONCEPTS
|
||||
/// The type of a container's elements.
|
||||
/** At the time of writing there's a similar thing in `std::experimental`,
|
||||
* which we may or may not end up using for this.
|
||||
*/
|
||||
template<typename CONTAINER>
|
||||
using value_type = strip_t<decltype(*std::begin(std::declval<CONTAINER>()))>;
|
||||
#endif // PQXX_HAVE_CONCEPTS
|
||||
|
||||
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
/// Concept: Any type that we can read as a string of `char`.
|
||||
template<typename STRING>
|
||||
concept char_string = std::ranges::contiguous_range<STRING> and
|
||||
std::same_as < strip_t<value_type<STRING>>,
|
||||
char > ;
|
||||
|
||||
/// Concept: Anything we can iterate to get things we can read as strings.
|
||||
template<typename RANGE>
|
||||
concept char_strings =
|
||||
std::ranges::range<RANGE> and char_string<strip_t<value_type<RANGE>>>;
|
||||
|
||||
/// Concept: Anything we might want to treat as binary data.
|
||||
template<typename DATA>
|
||||
concept potential_binary = std::ranges::contiguous_range<DATA> and
|
||||
(sizeof(value_type<DATA>) == 1);
|
||||
#endif // PQXX_HAVE_CONCEPTS
|
||||
|
||||
|
||||
// C++20: Retire these compatibility definitions.
|
||||
#if defined(PQXX_HAVE_CONCEPTS)
|
||||
|
||||
/// Template argument type for a range.
|
||||
/** This is a concept, so only available in C++20 or better. In pre-C++20
|
||||
* environments it's just an alias for @ref typename.
|
||||
*/
|
||||
# define PQXX_RANGE_ARG std::ranges::range
|
||||
|
||||
/// Template argument type for @ref char_string.
|
||||
/** This is a concept, so only available in C++20 or better. In pre-C++20
|
||||
* environments it's just an alias for @ref typename.
|
||||
*/
|
||||
# define PQXX_CHAR_STRING_ARG pqxx::char_string
|
||||
|
||||
/// Template argument type for @ref char_strings
|
||||
/** This is a concept, so only available in C++20 or better. In pre-C++20
|
||||
* environments it's just an alias for @ref typename.
|
||||
*/
|
||||
# define PQXX_CHAR_STRINGS_ARG pqxx::char_strings
|
||||
|
||||
#else // PQXX_HAVE_CONCEPTS
|
||||
|
||||
/// Template argument type for a range.
|
||||
/** This is a concept, so only available in C++20 or better. In pre-C++20
|
||||
* environments it's just an alias for @ref typename.
|
||||
*/
|
||||
# define PQXX_RANGE_ARG typename
|
||||
|
||||
/// Template argument type for @ref char_string.
|
||||
/** This is a concept, so only available in C++20 or better. In pre-C++20
|
||||
* environments it's just an alias for @ref typename.
|
||||
*/
|
||||
# define PQXX_CHAR_STRING_ARG typename
|
||||
|
||||
/// Template argument type for @ref char_strings
|
||||
/** This is a concept, so only available in C++20 or better. In pre-C++20
|
||||
* environments it's just an alias for @ref typename.
|
||||
*/
|
||||
# define PQXX_CHAR_STRINGS_ARG typename
|
||||
|
||||
#endif // PQXX_HAVE_CONCEPTS
|
||||
} // namespace pqxx
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user