Skip to content

Commit

Permalink
String conversion to string_view. More binary types. (#926)
Browse files Browse the repository at this point in the history
* Retire `binarystring`.

This type has been deprecated since 7.2.0, more than 4 years ago.

* String conversion to `string_view`.  More binary types.

Fixes: #694
Fixes: #827

Making much broader use of concepts.  String conversions now accept any
contiguous range of `std::byte` as binary data.  Traits specialisations
for integer and floating-point types are simpler now.  And you can now
just convert from string to `std::string_view` (or `char const *`), so
long as you don't access it after the original string's lifetime ends.

* Work around Visual Studio 2022 concepts problem.

This compiler was having trouble with the syntax I used to specialise
the generic `string_traits<T>` template to a _concept_ `T` (as opposed
to run-of-the-mill specialisation to a _type_ `T`).

So just for the floating-point string traits, I went back to the old
setup where I had a separate implementation type template
(`string_float_traits`) and derived the `string_traits` implementations
for those types from instantiations of that template.

* Forbid string conversion from `char const *`.

It was stupid of me to allow this.  I hope nobody ever used it.
  • Loading branch information
jtv committed Jan 19, 2025
1 parent 50c8d03 commit 5449a00
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 418 deletions.
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
8.0.0
- C++20 is now the oldest C++ version that libpqxx supports.
- "String conversion" to `std::string_view` is now supported. (#694)
- **Beware lifetime** when "converting" a string to `std::string_view`!
- Conversion from string to `char const *` is no longer allowed.
- Binary data can be any `std::contiguous_range` of `std::byte`. (#925)
- Retired `binarystring` and its headers. Use `blob` instead.
- Retired `connection_base` type alias. Use `connection`.
- Retired `pqxx::encrypt_password()`. Use the ones in `pqxx::connection`.
Expand Down
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
27 changes: 17 additions & 10 deletions include/pqxx/doc/datatypes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ You can "teach" libpqxx (in the scope of your own application) to convert
additional types of values to and from PostgreSQL's string format.

This is massively useful, but it's not for the faint of heart. You'll need to
specialise some templates. And, **the API for doing this can change with any
major libpqxx release.**
specialise several templates. And, **the API for doing this can change with
any major libpqxx release.**

If that happens, your code may fail to compile with the newer libpqxx version,
and you'll have to go through the `NEWS` file to find the API changes. Usually
Expand Down Expand Up @@ -107,13 +107,16 @@ namespace near the top of your translation unit, and pass the type as an
argument.

The library also provides specialisations for `std::optional<T>`,
`std::shared_ptr<T>`, and `std::unique_ptr<T>`. If you have conversions for
`T`, you'll also automatically have conversions for those.
`std::shared_ptr<T>`, and `std::unique_ptr<T>` (for any given `T`). If you
have conversions for `T`, you'll also automatically have conversions for those.


Specialise `type_name`
----------------------

(This is a feature that should disappear once we have introspection in the C++
language.)

When errors happen during conversion, libpqxx will compose error messages for
the user. Sometimes these will include the name of the type that's being
converted.
Expand Down Expand Up @@ -142,12 +145,16 @@ Specialise `nullness`
---------------------

A struct template `pqxx::nullness` defines whether your type has a natural
"null value" built in. If so, it also provides member functions for producing
and recognising null values.
"null value" built in. For example, a `std::optional` instantiation has a
value that neatly maps to an SQL null: the un-initialised state.

If your type has a value like that, its `pqxx::nullness` specialisation also
provides member functions for producing and recognising null values.

The simplest scenario is also the most common: most types don't have a null
value built in. There is no "null `int`" in C++. In that kind of case, just
derive your nullness traits from `pqxx::no_null` as a shorthand:
derive your nullness traits from `pqxx::no_null` as a shorthand: This tells
libpqxx that your type has no null value of its own.

```cxx
// T is your type.
Expand Down Expand Up @@ -196,9 +203,9 @@ where `NULL <> NULL`). Or `T` may have multiple different null values. Or `T`
may override the comparison operator to behave in some unusual way.
As a third case, your type may be one that _always_ represents a null value.
This is the case for `std::nullptr_t` and `std::nullopt_t`. In that case, you
set `nullness<TYPE>::always_null` to `true` (as well as `has_null` of course),
and you won't need to define any actual conversions.
This is the case for `std::nullptr_t` and `std::nullopt_t`. In a case like
that, you set `nullness<TYPE>::always_null` to `true` (as well as `has_null`
of course), and you won't need to define any actual conversions.
Specialise `string_traits`
Expand Down
45 changes: 7 additions & 38 deletions include/pqxx/field.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -344,44 +344,13 @@ template<> inline bool field::to<char const *>(char const *&obj) const
}


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())
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();
}


/// Specialization: `to(zview &)`.
/** This conversion is not generally available, since the general conversion
* would not know whether there was indeed a terminating zero at the end of
* the string. (It could check, but it would have no way of knowing that a
* zero occurring after the string in memory was actually part of the same
* allocation.)
*/
template<> inline bool field::to<zview>(zview &obj) const
{
bool const null{is_null()};
Expand Down
Loading

0 comments on commit 5449a00

Please sign in to comment.