Skip to content

Commit

Permalink
Accept pqxx::binary concept in more places.
Browse files Browse the repository at this point in the history
Fixes: #925

Extends a bunch more functions that accept `pqxx::bytes_view` with
variants which accept any type that satisfies the `pqxx::binary`
concept.

Also, change the `pqxx::bytes_view` type alies from being a
`std::basic_string_view<std::byte>` (which doesn't actually have to work
according to the standard!) to being a `std::span<std::byte>`.  This
seems to be broadly compatible with existing code.  For completeness I'm
adding a `pqxx::writable_bytes_view` as well.

Along the way I'm assuming support for C++17's `std::filesystem::path`,
and adding a conversion to `pqxx::zview`.  With that, `pqxx::blob` no
longer needs explicit support for `std::filesystem::path` filenames; it
just accepts `pqxx::zview` and passing a `std::filesystem::path` will
just work.  It avoids some ambiguities.
  • Loading branch information
jtv committed Jan 20, 2025
1 parent 34a5faa commit afe64a3
Show file tree
Hide file tree
Showing 17 changed files with 110 additions and 204 deletions.
3 changes: 2 additions & 1 deletion NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
- Assume compiler supports concepts.
- Assume compiler supports integral conversions in `charconv`.
- Assume compiler supports spans, ranges, and `cmp_less` etc.
- Assume compiler supports `std::remove_cvref_t`.
- Assume compiler supports `std::remove_cvref_t` etc.
- Assume compiler supports `std::filesystem::path`.
- Assume compiler supports `[[likely]]` & `[[unlikely]]`.
- Assume compiler supports `ssize()`.
- Assume compiler supports ISO-646 without needing `<ciso646>` header.
Expand Down
4 changes: 0 additions & 4 deletions cmake/pqxx_cxx_feature_checks.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ try_compile(
PQXX_HAVE_MULTIDIM ${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_MULTIDIM.cxx
)
try_compile(
PQXX_HAVE_PATH ${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_PATH.cxx
)
try_compile(
PQXX_HAVE_POLL ${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/PQXX_HAVE_POLL.cxx
Expand Down
9 changes: 0 additions & 9 deletions config-tests/PQXX_HAVE_PATH.cxx

This file was deleted.

2 changes: 1 addition & 1 deletion config/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ am__can_run_installinfo = \
esac
am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
am__DIST_COMMON = $(srcdir)/Makefile.in compile config.guess \
config.sub depcomp install-sh ltmain.sh missing mkinstalldirs
config.sub install-sh ltmain.sh missing mkinstalldirs
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
ACLOCAL = @ACLOCAL@
AMTAR = @AMTAR@
Expand Down
1 change: 0 additions & 1 deletion configitems
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ PQXX_HAVE_CXA_DEMANGLE internal compiler
PQXX_HAVE_GCC_PURE public compiler
PQXX_HAVE_GCC_VISIBILITY public compiler
PQXX_HAVE_MULTIDIM public compiler
PQXX_HAVE_PATH public compiler
PQXX_HAVE_POLL internal compiler
PQXX_HAVE_SLEEP_FOR internal compiler
PQXX_HAVE_SOURCE_LOCATION public compiler
Expand Down
36 changes: 0 additions & 36 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -17500,42 +17500,6 @@ fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_MULTIDIM" >&5
printf "%s\n" "$PQXX_HAVE_MULTIDIM" >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_PATH" >&5
printf %s "checking PQXX_HAVE_PATH... " >&6; }
PQXX_HAVE_PATH=yes
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
// Check for working std::filesystem support.

#include <filesystem>





int main()

{

// Apparently some versions of MinGW lack this comparison operator.

return std::filesystem::path{} != std::filesystem::path{};

}


_ACEOF
if ac_fn_cxx_try_compile "$LINENO"
then :

printf "%s\n" "#define PQXX_HAVE_PATH 1" >>confdefs.h

else $as_nop
PQXX_HAVE_PATH=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PQXX_HAVE_PATH" >&5
printf "%s\n" "$PQXX_HAVE_PATH" >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking PQXX_HAVE_POLL" >&5
printf %s "checking PQXX_HAVE_POLL... " >&6; }
PQXX_HAVE_POLL=yes
Expand Down
71 changes: 25 additions & 46 deletions include/pqxx/blob.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@

#include <cstdint>

#if defined(PQXX_HAVE_PATH)
# include <filesystem>
#endif

#include <ranges>
#include <span>

Expand Down Expand Up @@ -88,6 +84,7 @@ public:
*/
static constexpr std::size_t chunk_limit = 0x7fffffff;

// XXX: Can we build a generic version of this?
/// 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.
Expand All @@ -106,7 +103,7 @@ public:
* 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)
writable_bytes_view read(std::span<std::byte, extent> buf)
{
return buf.subspan(0, raw_read(std::data(buf), std::size(buf)));
}
Expand All @@ -117,7 +114,7 @@ public:
*
* Returns the filled portion of `buf`. This may be empty.
*/
template<binary DATA> std::span<std::byte> read(DATA &buf)
template<binary DATA> writable_bytes_view read(DATA &buf)
{
return {std::data(buf), raw_read(std::data(buf), std::size(buf))};
}
Expand All @@ -143,7 +140,7 @@ public:
*/
template<binary DATA> void write(DATA const &data)
{
raw_write(std::data(data), std::size(data));
return raw_write(binary_cast(data));
}

/// Resize large object to `size` bytes.
Expand Down Expand Up @@ -180,53 +177,47 @@ public:
*/
static oid from_buf(dbtransaction &tx, bytes_view data, oid id = 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.
*/
template<binary DATA>
static oid from_buf(dbtransaction &tx, DATA data, oid id = 0)
{
return from_buf(tx, binary_cast(data), id);
}

/// Append `data` to binary large object.
/** The underlying protocol only supports appending blocks up to 2 GB.
*/
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[]);

#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.
/// Append `data` to binary large object.
/** The underlying protocol only supports appending blocks up to 2 GB.
*/
[[nodiscard]] static oid
from_file(dbtransaction &tx, std::filesystem::path const &path)
template<binary DATA>
static void append_from_buf(dbtransaction &tx, DATA data, oid id)
{
return from_file(tx, path.c_str());
append_from_buf(tx, binary_cast(data), id);
}
#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);
[[nodiscard]] static oid from_file(dbtransaction &, zview path);

#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
static oid from_file(dbtransaction &, zview path, oid);

// XXX: Can we build a generic version of this?
/// 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, bytes &, std::size_t max_size);

// XXX: Can we build a generic version of this?
/// 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.
Expand All @@ -239,19 +230,7 @@ public:
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
static void to_file(dbtransaction &, oid, zview path);

/// Close this blob.
/** This does not delete the blob from the database; it only terminates your
Expand Down Expand Up @@ -282,7 +261,7 @@ private:
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);
void raw_write(bytes_view);

connection *m_conn = nullptr;
int m_fd = -1;
Expand Down
3 changes: 0 additions & 3 deletions include/pqxx/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@
/* Define if this feature is available. */
#undef PQXX_HAVE_MULTIDIM

/* Define if this feature is available. */
#undef PQXX_HAVE_PATH

/* Define if this feature is available. */
#undef PQXX_HAVE_POLL

Expand Down
8 changes: 8 additions & 0 deletions include/pqxx/connection.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,14 @@ public:
/// Escape and quote binary data for use as a BYTEA value in SQL statement.
[[nodiscard]] std::string quote(bytes_view bytes) const;

// TODO: Make "into buffer" variant to eliminate a string allocation.
/// Escape and quote binary data for use as a BYTEA value in SQL statement.
template<binary DATA>
[[nodiscard]] std::string quote(DATA data) const
{
return esc_raw(binary_cast(data));
}

// TODO: Make "into buffer" variant to eliminate a string allocation.
/// Escape string for literal LIKE match.
/** Use this when part of an SQL "LIKE" pattern should match only as a
Expand Down
46 changes: 14 additions & 32 deletions include/pqxx/doc/binary-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,33 @@ 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 `pqxx::bytes` or `pqxx::bytes_view`, or anything else that's a block of
contiguous `std::byte` in memory.
some block of contiguous `std::byte` values in memory. That could be a
`std::vector<std::byte>`, or `std::span<std::byte>`, and so on. However the
_preferred_ types for binary data in libpqxx are...
* `pqxx::bytes` for storing the data, similar to `std::string` for text.
* `pqxx::bytes_view` for reading data stored elsewhere, similar to how you'd
use `std::string_view` for text.
* `pqxx::writable_bytes_view` for writing to data stored elsewhere.

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
`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`
accompanied by a size (which could be signed or unsigned, and of any of a few
different widths). Sometimes that's your choice, or sometimes some other
library will dictate what form it takes.
`pqxx::blob` object. You might use that to write data which you pass in the
form of a `pqxx::bytes_view`. You might then read that data back by letting
`pqxx::blob` write the data into a `pqxx::bytes &` or a
`pqxx::writable_bytes_view` that you give it.

So long as it's _basically_ still a block of bytes though, you can use
`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()`:
`pqxx::binary_cast` to construct a `pqxx::bytes_view` from it:

```cxx
std::string hi{"Hello binary world"};
my_blob.write(pqxx::binary_cast(hi);
```
The other takes a pointer and a size:
For convenience there's also a form of `binary_cast` that takes a pointer and
a length.
```cxx
char const greeting[] = "Hello binary world";
char const *hi = greeting;
my_blob.write(pqxx::binary_cast(hi, sizeof(greeting)));
```


Caveats
-------

There are some restrictions on `binary_cast` that you must be aware of.

First, your data must of a type that gives us _bytes._ So: `char`,
`unsigned char`, `signed char`, `int8_t`, `uint8_t`, or of course `std::byte`.
You can't feed in a vector of `double`, or anything like that.

Second, the data must be laid out as a contiguous block in memory. If there's
no `std::data()` implementation for your type, it's not suitable.

Third, `binary_cast` only constructs something like a `std::string_view`. It
does not make a copy of your actual data. So, make sure that your data remains
alive and in the same place while you're using it.
2 changes: 2 additions & 0 deletions include/pqxx/params.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ public:
/// Append a non-null string parameter.
void append(std::string &&) &;

// XXX: Rethink the view/copy situation.
// XXX: Generic pqxx::binary support.
/// Append a non-null binary parameter.
/** The underlying data must stay valid for as long as the `params`
* remains active.
Expand Down
28 changes: 22 additions & 6 deletions include/pqxx/types.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -85,21 +85,30 @@ using value_type =
std::remove_cvref_t<decltype(*std::begin(std::declval<CONTAINER>()))>;


/// A type one byte in size.
template<typename CHAR>
concept char_sized = (sizeof(CHAR) == 1);


/// 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<std::remove_cvref_t<value_type<STRING>>, char>;
char_sized<value_type<STRING>> and
std::same_as<std::remove_reference_t<value_type<STRING>>, value_type<STRING>>;

/// 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<std::remove_cvref_t<value_type<RANGE>>>;
concept char_strings =
std::ranges::range<RANGE> and
char_string<std::remove_volatile_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);
std::ranges::contiguous_range<DATA> and
(sizeof(value_type<DATA>) == 1) and
not std::is_reference_v<value_type<DATA>>;


/// Concept: Binary string, akin to @c std::string for binary data.
Expand All @@ -112,8 +121,7 @@ concept potential_binary =
template<typename T>
concept binary =
std::ranges::contiguous_range<T> and
std::same_as<
std::remove_cvref_t<std::ranges::range_reference_t<T>>, std::byte>;
std::same_as<std::remove_cv_t<value_type<T>>, std::byte>;


/// A series of something that's not bytes.
Expand All @@ -126,6 +134,14 @@ concept nonbinary_range =
std::remove_cvref_t<std::ranges::range_reference_t<T>>, char>;


/// Type alias for a view of bytes.
using bytes_view = std::span<const std::byte>;


/// Type alias for a view of writable bytes.
using writable_bytes_view = std::span<std::byte>;


/// Marker for @ref stream_from constructors: "stream from table."
/** @deprecated Use @ref stream_from::table() instead.
*/
Expand Down
Loading

0 comments on commit afe64a3

Please sign in to comment.