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

1189 lines
34 KiB
C++

#include <array>
#include <cstring>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#if defined(PQXX_HAVE_SPAN) && __has_include(<span>)
# include <span>
#endif
#include <type_traits>
#include <variant>
#include <vector>
#include "pqxx/types.hxx"
#include "pqxx/util.hxx"
/* Internal helpers for string conversion, and conversion implementations.
*
* Do not include this header directly. The libpqxx headers do it for you.
*/
namespace pqxx::internal
{
/// Convert a number in [0, 9] to its ASCII digit.
inline constexpr char number_to_digit(int i) noexcept
{
return static_cast<char>(i + '0');
}
/// Compute numeric value of given textual digit (assuming that it is a digit).
constexpr int digit_to_number(char c) noexcept
{
return c - '0';
}
/// Summarize buffer overrun.
/** Don't worry about the exact parameter types: the sizes will be reasonably
* small, and nonnegative.
*/
std::string PQXX_LIBEXPORT
state_buffer_overrun(int have_bytes, int need_bytes);
template<typename HAVE, typename NEED>
inline std::string state_buffer_overrun(HAVE have_bytes, NEED need_bytes)
{
return state_buffer_overrun(
static_cast<int>(have_bytes), static_cast<int>(need_bytes));
}
/// Throw exception for attempt to convert null to given type.
[[noreturn]] PQXX_LIBEXPORT void
throw_null_conversion(std::string const &type);
/// Deliberately nonfunctional conversion traits for `char` types.
/** There are no string conversions for `char` and its signed and unsigned
* variants. Such a conversion would be dangerously ambiguous: should we treat
* it as text, or as a small integer? It'd be an open invitation for bugs.
*
* But the error message when you get this wrong is very cryptic. So, we
* derive dummy @ref string_traits implementations from this dummy type, and
* ensure that the compiler disallows their use. The compiler error message
* will at least contain a hint of the root of the problem.
*/
template<typename CHAR_TYPE> struct disallowed_ambiguous_char_conversion
{
static char *into_buf(char *, char *, CHAR_TYPE) = delete;
static constexpr zview
to_buf(char *, char *, CHAR_TYPE const &) noexcept = delete;
static constexpr std::size_t
size_buffer(CHAR_TYPE const &) noexcept = delete;
static CHAR_TYPE from_string(std::string_view) = delete;
};
template<typename T> PQXX_LIBEXPORT extern std::string to_string_float(T);
/// Generic implementation for into_buf, on top of to_buf.
template<typename T>
inline char *generic_into_buf(char *begin, char *end, T const &value)
{
zview const text{string_traits<T>::to_buf(begin, end, value)};
auto const space{end - begin};
// Include the trailing zero.
auto const len = std::size(text) + 1;
if (internal::cmp_greater(len, space))
throw conversion_overrun{
"Not enough buffer space to insert " + type_name<T> + ". " +
state_buffer_overrun(space, len)};
std::memmove(begin, text.data(), len);
return begin + len;
}
/// String traits for builtin integral types (though not bool).
template<typename T> struct integral_traits
{
static PQXX_LIBEXPORT T from_string(std::string_view text);
static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value);
static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value);
static constexpr std::size_t size_buffer(T const &) noexcept
{
/** Includes a sign if needed; the number of base-10 digits which the type
* can reliably represent; the one extra base-10 digit which the type can
* only partially represent; and the terminating zero.
*/
return std::is_signed_v<T> + std::numeric_limits<T>::digits10 + 1 + 1;
}
};
/// String traits for builtin floating-point types.
template<typename T> struct float_traits
{
static PQXX_LIBEXPORT T from_string(std::string_view text);
static PQXX_LIBEXPORT zview to_buf(char *begin, char *end, T const &value);
static PQXX_LIBEXPORT char *into_buf(char *begin, char *end, T const &value);
// Return a nonnegative integral value's number of decimal digits.
static constexpr std::size_t digits10(std::size_t value) noexcept
{
if (value < 10)
return 1;
else
return 1 + digits10(value / 10);
}
static constexpr std::size_t size_buffer(T const &) noexcept
{
using lims = std::numeric_limits<T>;
// See #328 for a detailed discussion on the maximum number of digits.
//
// In a nutshell: for the big cases, the scientific notation is always
// the shortest one, and therefore the one that to_chars will pick.
//
// So... How long can the scientific notation get? 1 (for sign) + 1 (for
// decimal point) + 1 (for 'e') + 1 (for exponent sign) + max_digits10 +
// max number of digits in the exponent + 1 (terminating zero).
//
// What's the max number of digits in the exponent? It's the max number of
// digits out of the most negative exponent and the most positive one.
//
// The longest positive exponent is easy: 1 + ceil(log10(max_exponent10)).
// (The extra 1 is because 10^n takes up 1 + n digits, not n.)
//
// The longest negative exponent is a bit harder: min_exponent10 gives us
// the smallest power of 10 which a normalised version of T can represent.
// But the smallest denormalised power of 10 that T can represent is
// another max_digits10 powers of 10 below that.
// needs a minus sign.
//
// All this stuff messes with my head a bit because it's on the order of
// log10(log10(n)). It's easy to get the number of logs wrong.
auto const max_pos_exp{digits10(lims::max_exponent10)};
// Really want std::abs(lims::min_exponent10), but MSVC 2017 apparently has
// problems with std::abs. So we use -lims::min_exponent10 instead.
auto const max_neg_exp{
digits10(lims::max_digits10 - lims::min_exponent10)};
return 1 + // Sign.
1 + // Decimal point.
std::numeric_limits<T>::max_digits10 + // Mantissa digits.
1 + // Exponent "e".
1 + // Exponent sign.
// Spell this weirdly to stop Windows compilers from reading this as
// a call to their "max" macro when NOMINMAX is not defined.
(std::max)(max_pos_exp, max_neg_exp) + // Exponent digits.
1; // Terminating zero.
}
};
} // namespace pqxx::internal
namespace pqxx
{
/// The built-in arithmetic types do not have inherent null values.
template<typename T>
struct nullness<T, std::enable_if_t<std::is_arithmetic_v<T>>> : no_null<T>
{};
template<> struct string_traits<short> : internal::integral_traits<short>
{};
template<> inline constexpr bool is_unquoted_safe<short>{true};
template<>
struct string_traits<unsigned short>
: internal::integral_traits<unsigned short>
{};
template<> inline constexpr bool is_unquoted_safe<unsigned short>{true};
template<> struct string_traits<int> : internal::integral_traits<int>
{};
template<> inline constexpr bool is_unquoted_safe<int>{true};
template<> struct string_traits<unsigned> : internal::integral_traits<unsigned>
{};
template<> inline constexpr bool is_unquoted_safe<unsigned>{true};
template<> struct string_traits<long> : internal::integral_traits<long>
{};
template<> inline constexpr bool is_unquoted_safe<long>{true};
template<>
struct string_traits<unsigned long> : internal::integral_traits<unsigned long>
{};
template<> inline constexpr bool is_unquoted_safe<unsigned long>{true};
template<>
struct string_traits<long long> : internal::integral_traits<long long>
{};
template<> inline constexpr bool is_unquoted_safe<long long>{true};
template<>
struct string_traits<unsigned long long>
: internal::integral_traits<unsigned long long>
{};
template<> inline constexpr bool is_unquoted_safe<unsigned long long>{true};
template<> struct string_traits<float> : internal::float_traits<float>
{};
template<> inline constexpr bool is_unquoted_safe<float>{true};
template<> struct string_traits<double> : internal::float_traits<double>
{};
template<> inline constexpr bool is_unquoted_safe<double>{true};
template<>
struct string_traits<long double> : internal::float_traits<long double>
{};
template<> inline constexpr bool is_unquoted_safe<long double>{true};
template<> struct string_traits<bool>
{
static PQXX_LIBEXPORT bool from_string(std::string_view text);
static constexpr zview to_buf(char *, char *, bool const &value) noexcept
{
return value ? "true"_zv : "false"_zv;
}
static char *into_buf(char *begin, char *end, bool const &value)
{
return pqxx::internal::generic_into_buf(begin, end, value);
}
static constexpr std::size_t size_buffer(bool const &) noexcept { return 6; }
};
/// We don't support conversion to/from `char` types.
/** Why are these disallowed? Because they are ambiguous. It's not inherently
* clear whether we should treat values of these types as text or as small
* integers. Either choice would lead to bugs.
*/
template<>
struct string_traits<char>
: internal::disallowed_ambiguous_char_conversion<char>
{};
/// We don't support conversion to/from `char` types.
/** Why are these disallowed? Because they are ambiguous. It's not inherently
* clear whether we should treat values of these types as text or as small
* integers. Either choice would lead to bugs.
*/
template<>
struct string_traits<signed char>
: internal::disallowed_ambiguous_char_conversion<signed char>
{};
/// We don't support conversion to/from `char` types.
/** Why are these disallowed? Because they are ambiguous. It's not inherently
* clear whether we should treat values of these types as text or as small
* integers. Either choice would lead to bugs.
*/
template<>
struct string_traits<unsigned char>
: internal::disallowed_ambiguous_char_conversion<unsigned char>
{};
template<> inline constexpr bool is_unquoted_safe<bool>{true};
template<typename T> struct nullness<std::optional<T>>
{
static constexpr bool has_null = true;
/// Technically, you could have an optional of an always-null type.
static constexpr bool always_null = nullness<T>::always_null;
static constexpr bool is_null(std::optional<T> const &v) noexcept
{
return ((not v.has_value()) or pqxx::is_null(*v));
}
static constexpr std::optional<T> null() { return {}; }
};
template<typename T>
inline constexpr format param_format(std::optional<T> const &value)
{
return param_format(*value);
}
template<typename T> struct string_traits<std::optional<T>>
{
static char *into_buf(char *begin, char *end, std::optional<T> const &value)
{
return string_traits<T>::into_buf(begin, end, *value);
}
static zview to_buf(char *begin, char *end, std::optional<T> const &value)
{
if (value.has_value())
return string_traits<T>::to_buf(begin, end, *value);
else
return {};
}
static std::optional<T> from_string(std::string_view text)
{
return std::optional<T>{
std::in_place, string_traits<T>::from_string(text)};
}
static std::size_t size_buffer(std::optional<T> const &value) noexcept
{
return pqxx::size_buffer(value.value());
}
};
template<typename T>
inline constexpr bool is_unquoted_safe<std::optional<T>>{is_unquoted_safe<T>};
template<typename... T> struct nullness<std::variant<T...>>
{
static constexpr bool has_null = (nullness<T>::has_null or ...);
static constexpr bool always_null = (nullness<T>::always_null and ...);
static constexpr bool is_null(std::variant<T...> const &value) noexcept
{
return std::visit(
[](auto const &i) noexcept {
return nullness<strip_t<decltype(i)>>::is_null(i);
},
value);
}
// We don't support `null()` for `std::variant`.
/** It would be technically possible to have a `null` in the case where just
* one of the types has a null, but it gets complicated and arbitrary.
*/
static constexpr std::variant<T...> null() = delete;
};
template<typename... T> struct string_traits<std::variant<T...>>
{
static char *
into_buf(char *begin, char *end, std::variant<T...> const &value)
{
return std::visit(
[begin, end](auto const &i) {
return string_traits<strip_t<decltype(i)>>::into_buf(begin, end, i);
},
value);
}
static zview to_buf(char *begin, char *end, std::variant<T...> const &value)
{
return std::visit(
[begin, end](auto const &i) {
return string_traits<strip_t<decltype(i)>>::to_buf(begin, end, i);
},
value);
}
static std::size_t size_buffer(std::variant<T...> const &value) noexcept
{
return std::visit(
[](auto const &i) noexcept { return pqxx::size_buffer(i); }, value);
}
/** There's no from_string for std::variant. We could have one with a rule
* like "pick the first type which fits the value," but we'd have to look
* into how natural that API feels to users.
*/
static std::variant<T...> from_string(std::string_view) = delete;
};
template<typename... Args>
inline constexpr format param_format(std::variant<Args...> const &value)
{
return std::visit([](auto &v) { return param_format(v); }, value);
}
template<typename... T>
inline constexpr bool is_unquoted_safe<std::variant<T...>>{
(is_unquoted_safe<T> and ...)};
template<typename T> inline T from_string(std::stringstream const &text)
{
return from_string<T>(text.str());
}
template<> struct string_traits<std::nullptr_t>
{
static char *into_buf(char *, char *, std::nullptr_t) = delete;
static constexpr zview
to_buf(char *, char *, std::nullptr_t const &) noexcept
{
return {};
}
static constexpr std::size_t size_buffer(std::nullptr_t = nullptr) noexcept
{
return 0;
}
static std::nullptr_t from_string(std::string_view) = delete;
};
template<> struct string_traits<std::nullopt_t>
{
static char *into_buf(char *, char *, std::nullopt_t) = delete;
static constexpr zview
to_buf(char *, char *, std::nullopt_t const &) noexcept
{
return {};
}
static constexpr std::size_t size_buffer(std::nullopt_t) noexcept
{
return 0;
}
static std::nullopt_t from_string(std::string_view) = delete;
};
template<> struct string_traits<std::monostate>
{
static char *into_buf(char *, char *, std::monostate) = delete;
static constexpr zview
to_buf(char *, char *, std::monostate const &) noexcept
{
return {};
}
static constexpr std::size_t size_buffer(std::monostate) noexcept
{
return 0;
}
static std::monostate from_string(std::string_view) = delete;
};
template<> inline constexpr bool is_unquoted_safe<std::nullptr_t>{true};
template<> struct nullness<char const *>
{
static constexpr bool has_null = true;
static constexpr bool always_null = false;
static constexpr bool is_null(char const *t) noexcept
{
return t == nullptr;
}
static constexpr char const *null() noexcept { return nullptr; }
};
/// String traits for C-style string ("pointer to char const").
template<> struct string_traits<char const *>
{
static char const *from_string(std::string_view text) { return text.data(); }
static zview to_buf(char *begin, char *end, char const *const &value)
{
return generic_to_buf(begin, end, value);
}
static char *into_buf(char *begin, char *end, char const *const &value)
{
auto const space{end - begin};
// Count the trailing zero, even though std::strlen() and friends don't.
auto const len{std::strlen(value) + 1};
if (space < ptrdiff_t(len))
throw conversion_overrun{
"Could not copy string: buffer too small. " +
pqxx::internal::state_buffer_overrun(space, len)};
std::memmove(begin, value, len);
return begin + len;
}
static std::size_t size_buffer(char const *const &value) noexcept
{
return std::strlen(value) + 1;
}
};
template<> struct nullness<char *>
{
static constexpr bool has_null = true;
static constexpr bool always_null = false;
static constexpr bool is_null(char const *t) noexcept
{
return t == nullptr;
}
static constexpr char const *null() { return nullptr; }
};
/// String traits for non-const C-style string ("pointer to char").
template<> struct string_traits<char *>
{
static char *into_buf(char *begin, char *end, char *const &value)
{
return string_traits<char const *>::into_buf(begin, end, value);
}
static zview to_buf(char *begin, char *end, char *const &value)
{
return string_traits<char const *>::to_buf(begin, end, value);
}
static std::size_t size_buffer(char *const &value) noexcept
{
return string_traits<char const *>::size_buffer(value);
}
/// Don't allow conversion to this type since it breaks const-safety.
static char *from_string(std::string_view) = delete;
};
template<std::size_t N> struct nullness<char[N]> : no_null<char[N]>
{};
/// String traits for C-style string constant ("array of char").
/** @warning This assumes that every array-of-char is a C-style string literal.
* So, it must include a trailing zero. and it must have static duration.
*/
template<std::size_t N> struct string_traits<char[N]>
{
static constexpr zview
to_buf(char *, char *, char const (&value)[N]) noexcept
{
return zview{value, N - 1};
}
static char *into_buf(char *begin, char *end, char const (&value)[N])
{
if (internal::cmp_less(end - begin, size_buffer(value)))
throw conversion_overrun{
"Could not convert char[] to string: too long for buffer."};
std::memcpy(begin, value, N);
return begin + N;
}
static constexpr std::size_t size_buffer(char const (&)[N]) noexcept
{
return N;
}
/// Don't allow conversion to this type.
static void from_string(std::string_view) = delete;
};
template<> struct nullness<std::string> : no_null<std::string>
{};
template<> struct string_traits<std::string>
{
static std::string from_string(std::string_view text)
{
return std::string{text};
}
static char *into_buf(char *begin, char *end, std::string const &value)
{
if (internal::cmp_greater_equal(std::size(value), end - begin))
throw conversion_overrun{
"Could not convert string to string: too long for buffer."};
// Include the trailing zero.
value.copy(begin, std::size(value));
begin[std::size(value)] = '\0';
return begin + std::size(value) + 1;
}
static zview to_buf(char *begin, char *end, std::string const &value)
{
return generic_to_buf(begin, end, value);
}
static std::size_t size_buffer(std::string const &value) noexcept
{
return std::size(value) + 1;
}
};
/// There's no real null for `std::string_view`.
/** I'm not sure how clear-cut this is: a `string_view` may have a null
* data pointer, which is analogous to a null `char` pointer.
*/
template<> struct nullness<std::string_view> : no_null<std::string_view>
{};
/// String traits for `string_view`.
template<> struct string_traits<std::string_view>
{
static constexpr std::size_t
size_buffer(std::string_view const &value) noexcept
{
return std::size(value) + 1;
}
static char *into_buf(char *begin, char *end, std::string_view const &value)
{
if (internal::cmp_greater_equal(std::size(value), end - begin))
throw conversion_overrun{
"Could not store string_view: too long for buffer."};
value.copy(begin, std::size(value));
begin[std::size(value)] = '\0';
return begin + std::size(value) + 1;
}
/// Don't convert to this type; it has nowhere to store its contents.
static std::string_view from_string(std::string_view) = delete;
};
template<> struct nullness<zview> : no_null<zview>
{};
/// String traits for `zview`.
template<> struct string_traits<zview>
{
static constexpr std::size_t
size_buffer(std::string_view const &value) noexcept
{
return std::size(value) + 1;
}
static char *into_buf(char *begin, char *end, zview const &value)
{
auto const size{std::size(value)};
if (internal::cmp_less_equal(end - begin, std::size(value)))
throw conversion_overrun{"Not enough buffer space to store this zview."};
value.copy(begin, size);
begin[size] = '\0';
return begin + size + 1;
}
static std::string_view to_buf(char *begin, char *end, zview const &value)
{
return {into_buf(begin, end, value), std::size(value)};
}
/// Don't convert to this type; it has nowhere to store its contents.
static zview from_string(std::string_view) = delete;
};
template<> struct nullness<std::stringstream> : no_null<std::stringstream>
{};
template<> struct string_traits<std::stringstream>
{
static std::size_t size_buffer(std::stringstream const &) = delete;
static std::stringstream from_string(std::string_view text)
{
std::stringstream stream;
stream.write(text.data(), std::streamsize(std::size(text)));
return stream;
}
static char *into_buf(char *, char *, std::stringstream const &) = delete;
static std::string_view
to_buf(char *, char *, std::stringstream const &) = delete;
};
template<> struct nullness<std::nullptr_t>
{
static constexpr bool has_null = true;
static constexpr bool always_null = true;
static constexpr bool is_null(std::nullptr_t const &) noexcept
{
return true;
}
static constexpr std::nullptr_t null() noexcept { return nullptr; }
};
template<> struct nullness<std::nullopt_t>
{
static constexpr bool has_null = true;
static constexpr bool always_null = true;
static constexpr bool is_null(std::nullopt_t const &) noexcept
{
return true;
}
static constexpr std::nullopt_t null() noexcept { return std::nullopt; }
};
template<> struct nullness<std::monostate>
{
static constexpr bool has_null = true;
static constexpr bool always_null = true;
static constexpr bool is_null(std::monostate const &) noexcept
{
return true;
}
static constexpr std::monostate null() noexcept { return {}; }
};
template<typename T> struct nullness<std::unique_ptr<T>>
{
static constexpr bool has_null = true;
static constexpr bool always_null = false;
static constexpr bool is_null(std::unique_ptr<T> const &t) noexcept
{
return not t or pqxx::is_null(*t);
}
static constexpr std::unique_ptr<T> null() { return {}; }
};
template<typename T, typename... Args>
struct string_traits<std::unique_ptr<T, Args...>>
{
static std::unique_ptr<T> from_string(std::string_view text)
{
return std::make_unique<T>(string_traits<T>::from_string(text));
}
static char *
into_buf(char *begin, char *end, std::unique_ptr<T, Args...> const &value)
{
return string_traits<T>::into_buf(begin, end, *value);
}
static zview
to_buf(char *begin, char *end, std::unique_ptr<T, Args...> const &value)
{
if (value)
return string_traits<T>::to_buf(begin, end, *value);
else
return {};
}
static std::size_t
size_buffer(std::unique_ptr<T, Args...> const &value) noexcept
{
return pqxx::size_buffer(*value.get());
}
};
template<typename T, typename... Args>
inline format param_format(std::unique_ptr<T, Args...> const &value)
{
return param_format(*value);
}
template<typename T, typename... Args>
inline constexpr bool is_unquoted_safe<std::unique_ptr<T, Args...>>{
is_unquoted_safe<T>};
template<typename T> struct nullness<std::shared_ptr<T>>
{
static constexpr bool has_null = true;
static constexpr bool always_null = false;
static constexpr bool is_null(std::shared_ptr<T> const &t) noexcept
{
return not t or pqxx::is_null(*t);
}
static constexpr std::shared_ptr<T> null() { return {}; }
};
template<typename T> struct string_traits<std::shared_ptr<T>>
{
static std::shared_ptr<T> from_string(std::string_view text)
{
return std::make_shared<T>(string_traits<T>::from_string(text));
}
static zview to_buf(char *begin, char *end, std::shared_ptr<T> const &value)
{
return string_traits<T>::to_buf(begin, end, *value);
}
static char *
into_buf(char *begin, char *end, std::shared_ptr<T> const &value)
{
return string_traits<T>::into_buf(begin, end, *value);
}
static std::size_t size_buffer(std::shared_ptr<T> const &value) noexcept
{
return pqxx::size_buffer(*value);
}
};
template<typename T> format param_format(std::shared_ptr<T> const &value)
{
return param_format(*value);
}
template<typename T>
inline constexpr bool is_unquoted_safe<std::shared_ptr<T>>{
is_unquoted_safe<T>};
template<>
struct nullness<std::basic_string<std::byte>>
: no_null<std::basic_string<std::byte>>
{};
#if defined(PQXX_HAVE_CONCEPTS)
template<binary DATA> struct nullness<DATA> : no_null<DATA>
{};
template<binary DATA> inline constexpr format param_format(DATA const &)
{
return format::binary;
}
template<binary DATA> struct string_traits<DATA>
{
static std::size_t size_buffer(DATA const &value) noexcept
{
return internal::size_esc_bin(std::size(value));
}
static zview to_buf(char *begin, char *end, DATA const &value)
{
return generic_to_buf(begin, end, value);
}
static char *into_buf(char *begin, char *end, DATA 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."};
internal::esc_bin(value, begin);
return begin + budget;
}
static DATA from_string(std::string_view text)
{
auto const size{pqxx::internal::size_unesc_bin(std::size(text))};
std::basic_string<std::byte> buf;
buf.resize(size);
pqxx::internal::unesc_bin(text, reinterpret_cast<std::byte *>(buf.data()));
return buf;
}
};
#endif // PQXX_HAVE_CONCEPTS
template<> struct string_traits<std::basic_string<std::byte>>
{
static std::size_t
size_buffer(std::basic_string<std::byte> const &value) noexcept
{
return internal::size_esc_bin(std::size(value));
}
static zview
to_buf(char *begin, char *end, std::basic_string<std::byte> const &value)
{
return generic_to_buf(begin, end, value);
}
static char *
into_buf(char *begin, char *end, std::basic_string<std::byte> 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."};
internal::esc_bin(value, begin);
return begin + budget;
}
static std::basic_string<std::byte> from_string(std::string_view text)
{
auto const size{pqxx::internal::size_unesc_bin(std::size(text))};
std::basic_string<std::byte> buf;
buf.resize(size);
pqxx::internal::unesc_bin(text, reinterpret_cast<std::byte *>(buf.data()));
return buf;
}
};
template<>
inline constexpr format param_format(std::basic_string<std::byte> const &)
{
return format::binary;
}
template<>
struct nullness<std::basic_string_view<std::byte>>
: no_null<std::basic_string_view<std::byte>>
{};
template<> struct string_traits<std::basic_string_view<std::byte>>
{
static std::size_t
size_buffer(std::basic_string_view<std::byte> const &value) noexcept
{
return internal::size_esc_bin(std::size(value));
}
static zview to_buf(
char *begin, char *end, std::basic_string_view<std::byte> const &value)
{
return generic_to_buf(begin, end, value);
}
static char *into_buf(
char *begin, char *end, std::basic_string_view<std::byte> 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."};
internal::esc_bin(value, begin);
return begin + budget;
}
// There's no from_string, because there's nobody to hold the data.
};
template<>
inline constexpr format param_format(std::basic_string_view<std::byte> const &)
{
return format::binary;
}
} // namespace pqxx
namespace pqxx::internal
{
/// String traits for SQL arrays.
template<typename Container> struct array_string_traits
{
private:
using elt_type = strip_t<value_type<Container>>;
using elt_traits = string_traits<elt_type>;
static constexpr zview s_null{"NULL"};
public:
static zview to_buf(char *begin, char *end, Container const &value)
{
return generic_to_buf(begin, end, value);
}
static char *into_buf(char *begin, char *end, Container const &value)
{
std::size_t const budget{size_buffer(value)};
if (internal::cmp_less(end - begin, budget))
throw conversion_overrun{
"Not enough buffer space to convert array to string."};
char *here = begin;
*here++ = '{';
bool nonempty{false};
for (auto const &elt : value)
{
if (is_null(elt))
{
s_null.copy(here, std::size(s_null));
here += std::size(s_null);
}
else if constexpr (is_sql_array<elt_type>)
{
// Render nested array in-place. Then erase the trailing zero.
here = elt_traits::into_buf(here, end, elt) - 1;
}
else if constexpr (is_unquoted_safe<elt_type>)
{
// No need to quote or escape. Just convert the value straight into
// its place in the array, and "backspace" the trailing zero.
here = elt_traits::into_buf(here, end, elt) - 1;
}
else
{
*here++ = '"';
// Use the tail end of the destination buffer as an intermediate
// buffer.
auto const elt_budget{pqxx::size_buffer(elt)};
for (char const c : elt_traits::to_buf(end - elt_budget, end, elt))
{
if (c == '\\' or c == '"')
*here++ = '\\';
*here++ = c;
}
*here++ = '"';
}
*here++ = array_separator<elt_type>;
nonempty = true;
}
// Erase that last comma, if present.
if (nonempty)
here--;
*here++ = '}';
*here++ = '\0';
return here;
}
static std::size_t size_buffer(Container const &value) noexcept
{
if constexpr (is_unquoted_safe<elt_type>)
return 3 + std::accumulate(
std::begin(value), std::end(value), std::size_t{},
[](std::size_t acc, elt_type const &elt) {
return acc +
(pqxx::is_null(elt) ?
std::size(s_null) :
elt_traits::size_buffer(elt)) -
1;
});
else
return 3 + std::accumulate(
std::begin(value), std::end(value), std::size_t{},
[](std::size_t acc, elt_type const &elt) {
// Opening and closing quotes, plus worst-case escaping,
// but don't count the trailing zeroes.
std::size_t const elt_size{
pqxx::is_null(elt) ? std::size(s_null) :
elt_traits::size_buffer(elt) - 1};
return acc + 2 * elt_size + 2;
});
}
// We don't yet support parsing of array types using from_string. Doing so
// would require a reference to the connection.
};
} // namespace pqxx::internal
namespace pqxx
{
template<typename T, typename... Args>
struct nullness<std::vector<T, Args...>> : no_null<std::vector<T>>
{};
template<typename T, typename... Args>
struct string_traits<std::vector<T, Args...>>
: internal::array_string_traits<std::vector<T, Args...>>
{};
/// We don't know how to pass array params in binary format, so pass as text.
template<typename T, typename... Args>
inline constexpr format param_format(std::vector<T, Args...> const &)
{
return format::text;
}
/// A `std::vector<std::byte>` is a binary string. Other vectors are not.
template<typename... Args>
inline constexpr format param_format(std::vector<std::byte, Args...> const &)
{
return format::binary;
}
template<typename T> inline constexpr bool is_sql_array<std::vector<T>>{true};
template<typename T, std::size_t N>
struct nullness<std::array<T, N>> : no_null<std::array<T, N>>
{};
template<typename T, std::size_t N>
struct string_traits<std::array<T, N>>
: internal::array_string_traits<std::array<T, N>>
{};
/// We don't know how to pass array params in binary format, so pass as text.
template<typename T, typename... Args, Args... args>
inline constexpr format param_format(std::array<T, args...> const &)
{
return format::text;
}
/// An array of `std::byte` is a binary string.
template<typename... Args, Args... args>
inline constexpr format param_format(std::array<std::byte, args...> const &)
{
return format::binary;
}
template<typename T, std::size_t N>
inline constexpr bool is_sql_array<std::array<T, N>>{true};
} // namespace pqxx
namespace pqxx
{
template<typename T> inline std::string to_string(T const &value)
{
if (is_null(value))
throw conversion_error{
"Attempt to convert null " + type_name<T> + " to a string."};
std::string buf;
// We can't just reserve() space; modifying the terminating zero leads to
// undefined behaviour.
buf.resize(size_buffer(value));
auto const data{buf.data()};
auto const end{
string_traits<T>::into_buf(data, data + std::size(buf), value)};
buf.resize(static_cast<std::size_t>(end - data - 1));
return buf;
}
template<> inline std::string to_string(float const &value)
{
return internal::to_string_float(value);
}
template<> inline std::string to_string(double const &value)
{
return internal::to_string_float(value);
}
template<> inline std::string to_string(long double const &value)
{
return internal::to_string_float(value);
}
template<> inline std::string to_string(std::stringstream const &value)
{
return value.str();
}
template<typename T> inline void into_string(T const &value, std::string &out)
{
if (is_null(value))
throw conversion_error{
"Attempt to convert null " + type_name<T> + " to a string."};
// We can't just reserve() data; modifying the terminating zero leads to
// undefined behaviour.
out.resize(size_buffer(value) + 1);
auto const data{out.data()};
auto const end{
string_traits<T>::into_buf(data, data + std::size(out), value)};
out.resize(static_cast<std::size_t>(end - data - 1));
}
} // namespace pqxx