Skip to content

Commit

Permalink
pqxx::byte and pqxx::bytes_view to support lack of std::char_traits<b…
Browse files Browse the repository at this point in the history
…yte> (#751)

The standard doesn't specify a generic implementation of std::char_traits, only specializations for certain types.
For a good reason: it's unlikely to be correct for all types that it'll compile with it.

All standard libraries today however do provide a generic implementation as an extension, though it's bound to be incorrect for some types.
libc++ has deprecated its in version 18 and will remove it in version 19 to eliminate hard to find correctness issues stemming from this.

Replace with type aliases that will use a custom char_traits when the standard library lacks such a generic implementation.
Note that we don't unconditionally use the custom char_traits variant as it's a source-breaking change requiring the users to update all usages of `std::string<std::byte>` to `pqxx::bytes` (i.e. `std::string<std::byte, pqxx::byte_char_traits>`). Ditto `std::string_view<std::byte>` and `pqxx::bytes_view`.
But for implementations lacking a generic implementation `std::string<std::byte>` and `std::string_view<std::byte>` wouldn't compile anyway so it's fine.

The aliases are named as `bytes` and `bytes_view` with the intetion of switching them to `std::vector<std::byte>` and `std::span<std::byte>` in a future major release that requires C++20.

Fixes: #726

By Raul Tambre <raul.tambre@clevon.com>
  • Loading branch information
tambry committed Feb 9, 2024
1 parent 6120486 commit 90f9c7c
Show file tree
Hide file tree
Showing 22 changed files with 278 additions and 228 deletions.
7 changes: 7 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
- Implement conversion from `string_view` to string. (#728)
- Rename `splitconfig` to `splitconfig.py`. (#763)
- Document need for conversions more clearly, link to docs. (#757)
- `std::basic_string<std::byte>` and `std::basic_string_view<std::byte>` have
been replaced with `pqxx::bytes` and `pqxx::bytes_view` respectively to
support the removal of generic `std::char_traits` in libc++ 19. Users must
switch to these aliases to support versions from libc++ 18 onward.
In a future major release that start requiring C++20 these are likely to
become aliases for `std::vector<std::byte>` and `std::span<std::byte>` to
better match the intent of the interfaces. (#726)
7.8.1
- Regenerate build files. Should fix ARM Mac build. (#715)
- Reinstate `<ciso646>` that MSVC can't live with or without. (#713)
Expand Down
13 changes: 6 additions & 7 deletions include/pqxx/binarystring.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ 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.
* @deprecated Use @c bytes and @c bytes_view 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.
*
Expand All @@ -59,7 +58,7 @@ class PQXX_LIBEXPORT binarystring
{
public:
using char_type = unsigned char;
using value_type = std::char_traits<char_type>::char_type;
using value_type = char_type;
using size_type = std::size_t;
using difference_type = long;
using const_reference = value_type const &;
Expand Down Expand Up @@ -174,10 +173,10 @@ public:
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
/// Read data as a @c bytes_view.
[[nodiscard]] pqxx::bytes_view bytes_view() const
{
return std::basic_string_view<std::byte>{bytes(), size()};
return pqxx::bytes_view{bytes(), size()};
}

private:
Expand Down
19 changes: 7 additions & 12 deletions include/pqxx/blob.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public:
* @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);
std::size_t read(bytes &buf, std::size_t size);

#if defined(PQXX_HAVE_SPAN)
/// Read up to `std::size(buf)` bytes from the object.
Expand Down Expand Up @@ -145,8 +145,7 @@ public:
*
* 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)
template<typename ALLOC> bytes_view read(std::vector<std::byte, ALLOC> &buf)
{
return {std::data(buf), raw_read(std::data(buf), std::size(buf))};
}
Expand Down Expand Up @@ -234,14 +233,12 @@ public:
/** 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);
static oid from_buf(dbtransaction &tx, bytes_view 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);
static void append_from_buf(dbtransaction &tx, bytes_view 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[]);
Expand Down Expand Up @@ -283,9 +280,7 @@ public:
/** 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);
static void to_buf(dbtransaction &, oid, bytes &, 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
Expand All @@ -295,8 +290,8 @@ public:
* `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);
dbtransaction &tx, oid id, std::int64_t offset, bytes &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[]);
Expand Down
25 changes: 10 additions & 15 deletions include/pqxx/connection.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ public:
"Not enough room to escape binary string of ", size, " byte(s): need ",
needed, " bytes of buffer space, but buffer size is ", space, ".")};

std::basic_string_view<std::byte> view{std::data(data), std::size(data)};
bytes_view view{std::data(data), std::size(data)};
auto const out{std::data(buffer)};
// Actually, in the modern format, we know beforehand exactly how many
// bytes we're going to fill. Just leave out the trailing zero.
Expand All @@ -747,13 +747,12 @@ public:

/// Escape binary string for use as SQL string literal on this connection.
/** You can also just use @ref esc with a binary string. */
[[nodiscard]] std::string esc_raw(std::basic_string_view<std::byte>) const;
[[nodiscard]] std::string esc_raw(bytes_view) const;

#if defined(PQXX_HAVE_SPAN)
/// Escape binary string for use as SQL string literal, into `buffer`.
/** You can also just use @ref esc with a binary string. */
[[nodiscard]] std::string
esc_raw(std::basic_string_view<std::byte>, std::span<char> buffer) const;
[[nodiscard]] std::string esc_raw(bytes_view, std::span<char> buffer) const;
#endif

#if defined(PQXX_HAVE_CONCEPTS)
Expand All @@ -762,8 +761,7 @@ public:
template<binary DATA>
[[nodiscard]] std::string esc_raw(DATA const &data) const
{
return esc_raw(
std::basic_string_view<std::byte>{std::data(data), std::size(data)});
return esc_raw(bytes_view{std::data(data), std::size(data)});
}
#endif

Expand All @@ -785,26 +783,24 @@ public:
* "bytea" escape format, used prior to PostgreSQL 9.0, is no longer
* supported.)
*/
[[nodiscard]] std::basic_string<std::byte>
unesc_bin(std::string_view text) const
[[nodiscard]] bytes unesc_bin(std::string_view text) const
{
std::basic_string<std::byte> buf;
bytes buf;
buf.resize(pqxx::internal::size_unesc_bin(std::size(text)));
pqxx::internal::unesc_bin(text, buf.data());
return buf;
}

/// Escape and quote a string of binary data.
std::string quote_raw(std::basic_string_view<std::byte>) const;
std::string quote_raw(bytes_view) const;

#if defined(PQXX_HAVE_CONCEPTS)
/// Escape and quote a string of binary data.
/** You can also just use @ref quote with binary data. */
template<binary DATA>
[[nodiscard]] std::string quote_raw(DATA const &data) const
{
return quote_raw(
std::basic_string_view<std::byte>{std::data(data), std::size(data)});
return quote_raw(bytes_view{std::data(data), std::size(data)});
}
#endif

Expand Down Expand Up @@ -856,8 +852,7 @@ public:

// TODO: Make "into buffer" variant to eliminate a string allocation.
/// Escape and quote binary data for use as a BYTEA value in SQL statement.
[[nodiscard]] std::string
quote(std::basic_string_view<std::byte> bytes) const;
[[nodiscard]] std::string quote(bytes_view bytes) const;

// TODO: Make "into buffer" variant to eliminate a string allocation.
/// Escape string for literal LIKE match.
Expand Down Expand Up @@ -920,7 +915,7 @@ public:
unesc_raw(char const text[]) const;

/// Escape and quote a string of binary data.
[[deprecated("Use quote(std::basic_string_view<std::byte>).")]] std::string
[[deprecated("Use quote(bytes_view).")]] std::string
quote_raw(unsigned char const bin[], std::size_t len) const;
//@}

Expand Down
8 changes: 4 additions & 4 deletions include/pqxx/doc/accessing-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ commands: `SELECT`, `VALUES`, or an `INSERT`, `UPDATE`, or `DELETE` with a
](https://www.postgresql.org/docs/current/sql-copy.html).
**Three,** when you convert a field to a "view" type (such as
`std::string_view` or `std::basic_string_view<std::byte>`), the view points to
underlying data which only stays valid until you iterate to the next row or
exit the loop. So if you want to use that data for longer than a single
iteration of the streaming loop, you'll have to store it somewhere yourself.
`std::string_view` or `pqxx::bytes_view`), the view points to underlying data
which only stays valid until you iterate to the next row or exit the loop. So
if you want to use that data for longer than a single iteration of the
streaming loop, you'll have to store it somewhere yourself.
Now for the good news. Streaming does make it very easy to query data and loop
over it, and often faster than with the "query" or "exec" functions:
Expand Down
9 changes: 4 additions & 5 deletions include/pqxx/doc/binary-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ Generally you'll want to use `BYTEA` for reasonably-sized values, and large
objects for very large values.

That's the database side. On the C++ side, in libpqxx, all binary data must be
either `std::basic_string<std::byte>` or `std::basic_string_view<std::byte>`;
or if you're building in C++20 or better, anything that's a block of
contiguous `std::byte` in memory.
either `pqxx::bytes` or `pqxx::bytes_view`; or if you're building in C++20 or
better, anything that's a block of contiguous `std::byte` in memory.

So for example, if you want to write a large object, you'd create a
`pqxx::blob` object. And you might use that to write data in the form of
`std::basic_string_view<std::byte>`.
`pqxx::bytes_view`.

Your particular binary data may look different though. You may have it in a
`std::string`, or a `std::vector<unsigned char>`, or a pointer to `char`
Expand All @@ -24,7 +23,7 @@ different widths). Sometimes that's your choice, or sometimes some other
library will dictate what form it takes.

So long as it's _basically_ still a block of bytes though, you can use
`pqxx::binary_cast` to construct a `std::basic_string_view<std::byte>` from it.
`pqxx::binary_cast` to construct a `pqxx::bytes_view` from it.

There are two forms of `binary_cast`. One takes a single argument that must
support `std::data()` and `std::size()`:
Expand Down
4 changes: 2 additions & 2 deletions include/pqxx/doc/prepared-statement.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,5 @@ data as the `BYTEA` type, or in binary large objects ("blobs").

In libpqxx, you represent binary data as a range of `std::byte`. They must be
contiguous in memory, so that libpqxx can pass pointers to the underlying C
library. So you might use `std::basic_string<std::byte>`, or
`std::basic_string_view<std::byte>`, or `std::vector<std::byte>`.
library. So you might use `pqxx::bytes`, or `pqxx::bytes_view`, or
`std::vector<std::byte>`.
2 changes: 1 addition & 1 deletion include/pqxx/field.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public:
*
* 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>>()`.
* example: `f.as<pqx::bytes>()`.
*/
[[nodiscard]] PQXX_PURE char const *c_str() const &;

Expand Down
34 changes: 17 additions & 17 deletions include/pqxx/internal/conversions.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -872,8 +872,8 @@ inline constexpr bool is_unquoted_safe<std::shared_ptr<T>>{


template<>
struct nullness<std::basic_string<std::byte>>
: no_null<std::basic_string<std::byte>>
struct nullness<bytes>
: no_null<bytes>
{};


Expand Down Expand Up @@ -916,7 +916,7 @@ template<binary DATA> struct string_traits<DATA>
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;
bytes buf;
buf.resize(size);
pqxx::internal::unesc_bin(text, reinterpret_cast<std::byte *>(buf.data()));
return buf;
Expand All @@ -925,25 +925,25 @@ template<binary DATA> struct string_traits<DATA>
#endif // PQXX_HAVE_CONCEPTS


template<> struct string_traits<std::basic_string<std::byte>>
template<> struct string_traits<bytes>
{
static constexpr bool converts_to_string{true};
static constexpr bool converts_from_string{true};

static std::size_t
size_buffer(std::basic_string<std::byte> const &value) noexcept
size_buffer(bytes 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)
to_buf(char *begin, char *end, bytes const &value)
{
return generic_to_buf(begin, end, value);
}

static char *
into_buf(char *begin, char *end, std::basic_string<std::byte> const &value)
into_buf(char *begin, char *end, bytes const &value)
{
auto const budget{size_buffer(value)};
if (internal::cmp_less(end - begin, budget))
Expand All @@ -953,10 +953,10 @@ template<> struct string_traits<std::basic_string<std::byte>>
return begin + budget;
}

static std::basic_string<std::byte> from_string(std::string_view text)
static bytes from_string(std::string_view text)
{
auto const size{pqxx::internal::size_unesc_bin(std::size(text))};
std::basic_string<std::byte> buf;
bytes buf;
buf.resize(size);
pqxx::internal::unesc_bin(text, reinterpret_cast<std::byte *>(buf.data()));
return buf;
Expand All @@ -965,37 +965,37 @@ template<> struct string_traits<std::basic_string<std::byte>>


template<>
inline constexpr format param_format(std::basic_string<std::byte> const &)
inline constexpr format param_format(bytes const &)
{
return format::binary;
}


template<>
struct nullness<std::basic_string_view<std::byte>>
: no_null<std::basic_string_view<std::byte>>
struct nullness<bytes_view>
: no_null<bytes_view>
{};


template<> struct string_traits<std::basic_string_view<std::byte>>
template<> struct string_traits<bytes_view>
{
static constexpr bool converts_to_string{true};
static constexpr bool converts_from_string{false};

static std::size_t
size_buffer(std::basic_string_view<std::byte> const &value) noexcept
size_buffer(bytes_view 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)
char *begin, char *end, bytes_view 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)
char *begin, char *end, bytes_view const &value)
{
auto const budget{size_buffer(value)};
if (internal::cmp_less(end - begin, budget))
Expand All @@ -1009,7 +1009,7 @@ template<> struct string_traits<std::basic_string_view<std::byte>>
};

template<>
inline constexpr format param_format(std::basic_string_view<std::byte> const &)
inline constexpr format param_format(bytes_view const &)
{
return format::binary;
}
Expand Down

0 comments on commit 90f9c7c

Please sign in to comment.