range.hxx
#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 <utility>
#include <variant>
#include "pqxx/internal/array-composite.hxx"
#include "pqxx/internal/concat.hxx"
namespace pqxx
{
struct no_bound
{
template<typename TYPE>
constexpr bool extends_down_to(TYPE const &) const noexcept
{
return true;
}
template<typename TYPE>
constexpr bool extends_up_to(TYPE const &) const noexcept
{
return true;
}
};
template<typename TYPE> class inclusive_bound
{
// (Putting private section first to work around bug in gcc < 10: see #665.)
private:
TYPE m_value;
public:
inclusive_bound() = delete;
constexpr 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; }
[[nodiscard]] constexpr bool extends_down_to(TYPE const &value) const
noexcept(noexcept(value < m_value))
{
return not(value < m_value);
}
[[nodiscard]] constexpr bool extends_up_to(TYPE const &value) const
noexcept(noexcept(value < m_value))
{
return not(m_value < value);
}
};
template<typename TYPE> class exclusive_bound
{
// (Putting private section first to work around bug in gcc < 10: see #665.)
private:
TYPE m_value;
public:
exclusive_bound() = delete;
constexpr 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; }
[[nodiscard]] constexpr bool extends_down_to(TYPE const &value) const
noexcept(noexcept(m_value < value))
{
return m_value < value;
}
[[nodiscard]] constexpr bool extends_up_to(TYPE const &value) const
noexcept(noexcept(value < m_value))
{
return value < m_value;
}
};
template<typename TYPE> class range_bound
{
// (Putting private section first to work around bug in gcc < 10: see #665.)
private:
std::variant<no_bound, inclusive_bound<TYPE>, exclusive_bound<TYPE>> m_bound;
public:
range_bound() = delete;
constexpr range_bound(no_bound) noexcept : m_bound{} {}
constexpr range_bound(inclusive_bound<TYPE> const &bound) noexcept(
noexcept(inclusive_bound<TYPE>{bound})) :
m_bound{bound}
{}
constexpr range_bound(exclusive_bound<TYPE> const &bound) noexcept(
noexcept(exclusive_bound{bound})) :
m_bound{bound}
{}
constexpr range_bound(range_bound const &) noexcept(
noexcept(inclusive_bound<TYPE>{
std::declval<inclusive_bound<TYPE> const &>()})
and noexcept(exclusive_bound<TYPE>{
std::declval<exclusive_bound<TYPE> const &>()})) = default;
constexpr range_bound(range_bound &&) = default;
constexpr bool operator==(range_bound const &rhs) const
noexcept(noexcept(*this->value() == *rhs.value()))
{
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();
}
constexpr bool operator!=(range_bound const &rhs) const
noexcept(noexcept(*this == rhs))
{
return not(*this == rhs);
}
range_bound &operator=(range_bound const &) = default;
range_bound &operator=(range_bound &&) = default;
constexpr bool is_limited() const noexcept
{
return not std::holds_alternative<no_bound>(m_bound);
}
constexpr bool is_inclusive() const noexcept
{
return std::holds_alternative<inclusive_bound<TYPE>>(m_bound);
}
constexpr bool is_exclusive() const noexcept
{
return std::holds_alternative<exclusive_bound<TYPE>>(m_bound);
}
constexpr bool extends_down_to(TYPE const &value) const
{
return std::visit(
[&value](auto const &bound) noexcept(noexcept(bound.extends_down_to(
value))) { return bound.extends_down_to(value); },
m_bound);
}
constexpr bool extends_up_to(TYPE const &value) const
{
return std::visit(
[&value](auto const &bound) noexcept(noexcept(
bound.extends_up_to(value))) { return bound.extends_up_to(value); },
m_bound);
}
[[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);
}
};
// C++20: Concepts for comparisons, construction, etc.
template<typename TYPE> class range
{
// (Putting private section first to work around bug in gcc < 10: see #665.)
private:
range_bound<TYPE> m_lower, m_upper;
public:
constexpr 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(), ").")};
}
constexpr range() noexcept(noexcept(exclusive_bound<TYPE>{TYPE{}})) :
m_lower{exclusive_bound<TYPE>{TYPE{}}},
m_upper{exclusive_bound<TYPE>{TYPE{}}}
{}
constexpr bool operator==(range const &rhs) const
noexcept(noexcept(this->lower_bound() == rhs.lower_bound()) and noexcept(
this->upper_bound() == rhs.upper_bound()) and noexcept(this->empty()))
{
return (this->lower_bound() == rhs.lower_bound() and
this->upper_bound() == rhs.upper_bound()) or
(this->empty() and rhs.empty());
}
constexpr bool operator!=(range const &rhs) const
noexcept(noexcept(*this == rhs))
{
return not(*this == rhs);
}
range(range const &) = default;
range(range &&) = default;
range &operator=(range const &) = default;
range &operator=(range &&) = default;
constexpr bool empty() const
noexcept(noexcept(m_lower.is_exclusive()) and noexcept(
m_lower.is_limited()) and noexcept(*m_lower.value() < *m_upper.value()))
{
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());
}
constexpr bool contains(TYPE value) const noexcept(noexcept(
m_lower.extends_down_to(value)) and noexcept(m_upper.extends_up_to(value)))
{
return m_lower.extends_down_to(value) and m_upper.extends_up_to(value);
}
constexpr bool contains(range<TYPE> const &other) const
noexcept(noexcept((*this & other) == other))
{
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;
}
constexpr 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};
}
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};
}
};
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)};
}
// 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.
auto const field_parser{
pqxx::internal::specialize_parse_composite_field<std::optional<TYPE>>(
pqxx::internal::encoding_group::UTF8)};
field_parser(index, text, pos, lower, last);
field_parser(index, text, pos, upper, 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};
static std::string err_bad_input(std::string_view text)
{
return internal::concat("Invalid range input: '", text, "'");
}
};
template<typename TYPE> struct nullness<range<TYPE>> : no_null<range<TYPE>>
{};
} // namespace pqxx
#endif
pqxx
The home of all libpqxx classes, functions, templates, etc.
Definition array.hxx:33
pqxx::format::text
@ text
pqxx::argument_error
Invalid argument passed to libpqxx, similar to std::invalid_argument.
Definition except.hxx:266
pqxx::conversion_error
Value conversion failed, e.g. when converting "Hello" to int.
Definition except.hxx:283
pqxx::conversion_overrun
Could not convert value to string: not enough buffer space.
Definition except.hxx:313
pqxx::range_error
Something is out of range, similar to std::out_of_range.
Definition except.hxx:326
pqxx::no_bound
An unlimited boundary value to a pqxx::range.
Definition range.hxx:24
pqxx::no_bound::extends_down_to
constexpr bool extends_down_to(TYPE const &) const noexcept
Definition range.hxx:26
pqxx::no_bound::extends_up_to
constexpr bool extends_up_to(TYPE const &) const noexcept
Definition range.hxx:31
pqxx::inclusive_bound
An inclusive boundary value to a pqxx::range.
Definition range.hxx:43
pqxx::inclusive_bound::inclusive_bound
constexpr inclusive_bound(TYPE const &value)
Definition range.hxx:50
pqxx::inclusive_bound::extends_down_to
constexpr bool extends_down_to(TYPE const &value) const noexcept(noexcept(value< m_value))
Would this bound, as a lower bound, include value?
Definition range.hxx:59
pqxx::inclusive_bound::inclusive_bound
inclusive_bound()=delete
pqxx::inclusive_bound::get
constexpr TYPE const & get() const &noexcept
Definition range.hxx:56
pqxx::inclusive_bound::extends_up_to
constexpr bool extends_up_to(TYPE const &value) const noexcept(noexcept(value< m_value))
Would this bound, as an upper bound, include value?
Definition range.hxx:66
pqxx::exclusive_bound
An exclusive boundary value to a pqxx::range.
Definition range.hxx:79
pqxx::exclusive_bound::extends_up_to
constexpr bool extends_up_to(TYPE const &value) const noexcept(noexcept(value< m_value))
Would this bound, as an upper bound, include value?
Definition range.hxx:102
pqxx::exclusive_bound::exclusive_bound
exclusive_bound()=delete
pqxx::exclusive_bound::exclusive_bound
constexpr exclusive_bound(TYPE const &value)
Definition range.hxx:86
pqxx::exclusive_bound::get
constexpr TYPE const & get() const &noexcept
Definition range.hxx:92
pqxx::exclusive_bound::extends_down_to
constexpr bool extends_down_to(TYPE const &value) const noexcept(noexcept(m_value< value))
Would this bound, as a lower bound, include value?
Definition range.hxx:95
pqxx::range_bound
A range boundary value.
Definition range.hxx:115
pqxx::range_bound::extends_up_to
constexpr bool extends_up_to(TYPE const &value) const
Would this bound, as an upper bound, include value?
Definition range.hxx:189
pqxx::range_bound::range_bound
constexpr range_bound(range_bound const &) noexcept(noexcept(inclusive_bound< TYPE >{ std::declval< inclusive_bound< TYPE > const & >()}) and noexcept(exclusive_bound< TYPE >{ std::declval< exclusive_bound< TYPE > const & >()}))=default
pqxx::range_bound::is_exclusive
constexpr bool is_exclusive() const noexcept
Is this boundary an exclusive one?
Definition range.hxx:174
pqxx::range_bound::range_bound
constexpr range_bound(inclusive_bound< TYPE > const &bound) noexcept(noexcept(inclusive_bound< TYPE >{bound}))
Definition range.hxx:124
pqxx::range_bound::is_limited
constexpr bool is_limited() const noexcept
Is this a finite bound?
Definition range.hxx:162
pqxx::range_bound::value
constexpr TYPE const * value() const &noexcept
Return bound value, or nullptr if it's not limited.
Definition range.hxx:198
pqxx::range_bound::operator!=
constexpr bool operator!=(range_bound const &rhs) const noexcept(noexcept(*this==rhs))
Definition range.hxx:153
pqxx::range_bound::extends_down_to
constexpr bool extends_down_to(TYPE const &value) const
Would this bound, as a lower bound, include value?
Definition range.hxx:180
pqxx::range_bound::range_bound
range_bound()=delete
pqxx::range_bound::range_bound
constexpr range_bound(no_bound) noexcept
Definition range.hxx:122
pqxx::range_bound::operator=
range_bound & operator=(range_bound const &)=default
pqxx::range_bound::operator==
constexpr bool operator==(range_bound const &rhs) const noexcept(noexcept(*this->value()== *rhs.value()))
Definition range.hxx:142
pqxx::range_bound::range_bound
constexpr range_bound(exclusive_bound< TYPE > const &bound) noexcept(noexcept(exclusive_bound{bound}))
Definition range.hxx:129
pqxx::range_bound::is_inclusive
constexpr bool is_inclusive() const noexcept
Is this boundary an inclusive one?
Definition range.hxx:168
pqxx::range_bound::operator=
range_bound & operator=(range_bound &&)=default
pqxx::range_bound::range_bound
constexpr range_bound(range_bound &&)=default
pqxx::range
A C++ equivalent to PostgreSQL's range types.
Definition range.hxx:234
pqxx::range::operator&
constexpr range operator&(range const &other) const
Intersection of two ranges.
Definition range.hxx:334
pqxx::range::operator==
constexpr bool operator==(range const &rhs) const noexcept(noexcept(this->lower_bound()==rhs.lower_bound()) and noexcept(this->upper_bound()==rhs.upper_bound()) and noexcept(this->empty()))
Definition range.hxx:265
pqxx::range::contains
constexpr bool contains(TYPE value) const noexcept(noexcept(m_lower.extends_down_to(value)) and noexcept(m_upper.extends_up_to(value)))
Does this range encompass value?
Definition range.hxx:304
pqxx::range::contains
constexpr bool contains(range< TYPE > const &other) const noexcept(noexcept((*this &other)==other))
Does this range encompass all of other?
Definition range.hxx:314
pqxx::range::operator!=
constexpr bool operator!=(range const &rhs) const noexcept(noexcept(*this==rhs))
Definition range.hxx:274
pqxx::range::range
range(range &&)=default
pqxx::range::range
range(range const &)=default
pqxx::range::operator=
range & operator=(range &&)=default
pqxx::range::operator=
range & operator=(range const &)=default
pqxx::range::range
constexpr range(range_bound< TYPE > lower, range_bound< TYPE > upper)
Create a range.
Definition range.hxx:245
pqxx::range::upper_bound
constexpr range_bound< TYPE > const & upper_bound() const &noexcept
Definition range.hxx:326
pqxx::range::empty
constexpr bool empty() const noexcept(noexcept(m_lower.is_exclusive()) and noexcept(m_lower.is_limited()) and noexcept(*m_lower.value()< *m_upper.value()))
Is this range clearly empty?
Definition range.hxx:294
pqxx::range::lower_bound
constexpr range_bound< TYPE > const & lower_bound() const &noexcept
Definition range.hxx:321
pqxx::range::range
constexpr range() noexcept(noexcept(exclusive_bound< TYPE >{TYPE{}}))
Create an empty range.
Definition range.hxx:260
pqxx::string_traits< range< TYPE > >::size_buffer
static constexpr std::size_t size_buffer(range< TYPE > const &value) noexcept
Definition range.hxx:509
pqxx::string_traits< range< TYPE > >::into_buf
static char * into_buf(char *begin, char *end, range< TYPE > const &value)
Definition range.hxx:404
pqxx::string_traits< range< TYPE > >::to_buf
static zview to_buf(char *begin, char *end, range< TYPE > const &value)
Definition range.hxx:398
pqxx::string_traits< range< TYPE > >::from_string
static range< TYPE > from_string(std::string_view text)
Definition range.hxx:439
pqxx::nullness
Traits describing a type's "null value," if any.
Definition strconv.hxx:93
pqxx::no_null
Nullness traits describing a type which does not have a null value.
Definition strconv.hxx:115
pqxx::string_traits
Traits class for use in string conversions.
Definition strconv.hxx:155
pqxx::zview
Marker-type wrapper: zero-terminated std::string_view.
Definition zview.hxx:38