First Commit
Some checks failed
/ build_macos (push) Has been cancelled
/ build_windows (push) Has been cancelled
/ build_ubuntu (push) Has been cancelled

This commit is contained in:
2025-11-19 16:23:45 +07:00
commit dbdc5bcc4a
1791 changed files with 489451 additions and 0 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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 */

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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 &param : 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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -0,0 +1,3 @@
/// @deprecated Include @c <pqxx/params> instead.
#include "params.hxx"

View File

@@ -0,0 +1,3 @@
/// @deprecated Include @c <pqxx/params> instead.
#include "params.hxx"

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View 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"

View 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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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