libpqxx
The C++ client library for PostgreSQL
range.hxx
1 #ifndef PQXX_H_RANGE
2 #define PQXX_H_RANGE
3 
4 #if !defined(PQXX_HEADER_PRE)
5 # error "Include libpqxx headers as <pqxx/header>, not <pqxx/header.hxx>."
6 #endif
7 
8 #include <utility>
9 #include <variant>
10 
11 #include "pqxx/internal/array-composite.hxx"
12 #include "pqxx/internal/concat.hxx"
13 
14 namespace pqxx
15 {
17 
23 struct no_bound
24 {
25  template<typename TYPE>
26  constexpr bool extends_down_to(TYPE const &) const noexcept
27  {
28  return true;
29  }
30  template<typename TYPE>
31  constexpr bool extends_up_to(TYPE const &) const noexcept
32  {
33  return true;
34  }
35 };
36 
37 
39 
42 template<typename TYPE> class inclusive_bound
43 {
44  // (Putting private section first to work around bug in gcc < 10: see #665.)
45 private:
46  TYPE m_value;
47 
48 public:
49  inclusive_bound() = delete;
50  constexpr explicit inclusive_bound(TYPE const &value) : m_value{value}
51  {
52  if (is_null(value))
53  throw argument_error{"Got null value as an inclusive range bound."};
54  }
55 
56  [[nodiscard]] constexpr TYPE const &get() const & noexcept
57  {
58  return m_value;
59  }
60 
62  [[nodiscard]] constexpr bool extends_down_to(TYPE const &value) const
63  noexcept(noexcept(value < m_value))
64  {
65  return not(value < m_value);
66  }
67 
69  [[nodiscard]] constexpr bool extends_up_to(TYPE const &value) const
70  noexcept(noexcept(value < m_value))
71  {
72  return not(m_value < value);
73  }
74 };
75 
76 
78 
81 template<typename TYPE> class exclusive_bound
82 {
83  // (Putting private section first to work around bug in gcc < 10: see #665.)
84 private:
85  TYPE m_value;
86 
87 public:
88  exclusive_bound() = delete;
89  constexpr explicit exclusive_bound(TYPE const &value) : m_value{value}
90  {
91  if (is_null(value))
92  throw argument_error{"Got null value as an exclusive range bound."};
93  }
94 
95  [[nodiscard]] constexpr TYPE const &get() const & noexcept
96  {
97  return m_value;
98  }
99 
101  [[nodiscard]] constexpr bool extends_down_to(TYPE const &value) const
102  noexcept(noexcept(m_value < value))
103  {
104  return m_value < value;
105  }
106 
108  [[nodiscard]] constexpr bool extends_up_to(TYPE const &value) const
109  noexcept(noexcept(value < m_value))
110  {
111  return value < m_value;
112  }
113 };
114 
115 
117 
120 template<typename TYPE> class range_bound
121 {
122  // (Putting private section first to work around bug in gcc < 10: see #665.)
123 private:
124  std::variant<no_bound, inclusive_bound<TYPE>, exclusive_bound<TYPE>> m_bound;
125 
126 public:
127  range_bound() = delete;
128  constexpr range_bound(no_bound) noexcept : m_bound{} {}
129 
130  constexpr range_bound(inclusive_bound<TYPE> const &bound) noexcept(
131  noexcept(inclusive_bound<TYPE>{bound})) :
132  m_bound{bound}
133  {}
134 
135  constexpr range_bound(exclusive_bound<TYPE> const &bound) noexcept(
136  noexcept(exclusive_bound{bound})) :
137  m_bound{bound}
138  {}
139 
140  constexpr range_bound(range_bound const &) noexcept(
141  noexcept(inclusive_bound<TYPE>{
142  std::declval<inclusive_bound<TYPE> const &>()}) and
143  noexcept(exclusive_bound<TYPE>{
144  std::declval<exclusive_bound<TYPE> const &>()})) = default;
145 
146  constexpr range_bound(range_bound &&) = default;
147 
148  constexpr bool operator==(range_bound const &rhs) const
149  noexcept(noexcept(*this->value() == *rhs.value()))
150  {
151  if (this->is_limited())
152  return (
153  rhs.is_limited() and (this->is_inclusive() == rhs.is_inclusive()) and
154  (*this->value() == *rhs.value()));
155  else
156  return not rhs.is_limited();
157  }
158 
159  constexpr bool operator!=(range_bound const &rhs) const
160  noexcept(noexcept(*this == rhs))
161  {
162  return not(*this == rhs);
163  }
164  range_bound &operator=(range_bound const &) = default;
165  range_bound &operator=(range_bound &&) = default;
166 
168  constexpr bool is_limited() const noexcept
169  {
170  return not std::holds_alternative<no_bound>(m_bound);
171  }
172 
174  constexpr bool is_inclusive() const noexcept
175  {
176  return std::holds_alternative<inclusive_bound<TYPE>>(m_bound);
177  }
178 
180  constexpr bool is_exclusive() const noexcept
181  {
182  return std::holds_alternative<exclusive_bound<TYPE>>(m_bound);
183  }
184 
186  constexpr bool extends_down_to(TYPE const &value) const
187  {
188  return std::visit(
189  [&value](auto const &bound) noexcept(noexcept(bound.extends_down_to(
190  value))) { return bound.extends_down_to(value); },
191  m_bound);
192  }
193 
195  constexpr bool extends_up_to(TYPE const &value) const
196  {
197  return std::visit(
198  [&value](auto const &bound) noexcept(noexcept(
199  bound.extends_up_to(value))) { return bound.extends_up_to(value); },
200  m_bound);
201  }
202 
204  [[nodiscard]] constexpr TYPE const *value() const & noexcept
205  {
206  return std::visit(
207  [](auto const &bound) noexcept {
208  using bound_t = std::decay_t<decltype(bound)>;
209  if constexpr (std::is_same_v<bound_t, no_bound>)
210  return static_cast<TYPE const *>(nullptr);
211  else
212  return &bound.get();
213  },
214  m_bound);
215  }
216 };
217 
218 
219 // C++20: Concepts for comparisons, construction, etc.
221 
239 template<typename TYPE> class range
240 {
241  // (Putting private section first to work around bug in gcc < 10: see #665.)
242 private:
243  range_bound<TYPE> m_lower, m_upper;
244 
245 public:
247 
251  constexpr range(range_bound<TYPE> lower, range_bound<TYPE> upper) :
252  m_lower{lower}, m_upper{upper}
253  {
254  if (
255  lower.is_limited() and upper.is_limited() and
256  (*upper.value() < *lower.value()))
257  throw range_error{internal::concat(
258  "Range's lower bound (", *lower.value(),
259  ") is greater than its upper bound (", *upper.value(), ").")};
260  }
261 
263 
266  constexpr range() noexcept(noexcept(exclusive_bound<TYPE>{TYPE{}})) :
267  m_lower{exclusive_bound<TYPE>{TYPE{}}},
268  m_upper{exclusive_bound<TYPE>{TYPE{}}}
269  {}
270 
271  constexpr bool operator==(range const &rhs) const noexcept(
272  noexcept(this->lower_bound() == rhs.lower_bound()) and
273  noexcept(this->upper_bound() == rhs.upper_bound()) and
274  noexcept(this->empty()))
275  {
276  return (this->lower_bound() == rhs.lower_bound() and
277  this->upper_bound() == rhs.upper_bound()) or
278  (this->empty() and rhs.empty());
279  }
280 
281  constexpr bool operator!=(range const &rhs) const
282  noexcept(noexcept(*this == rhs))
283  {
284  return not(*this == rhs);
285  }
286 
287  range(range const &) = default;
288  range(range &&) = default;
289  range &operator=(range const &) = default;
290  range &operator=(range &&) = default;
291 
293 
301  constexpr bool empty() const noexcept(
302  noexcept(m_lower.is_exclusive()) and noexcept(m_lower.is_limited()) and
303  noexcept(*m_lower.value() < *m_upper.value()))
304  {
305  return (m_lower.is_exclusive() or m_upper.is_exclusive()) and
306  m_lower.is_limited() and m_upper.is_limited() and
307  not(*m_lower.value() < *m_upper.value());
308  }
309 
311  constexpr bool contains(TYPE value) const noexcept(
312  noexcept(m_lower.extends_down_to(value)) and
313  noexcept(m_upper.extends_up_to(value)))
314  {
315  return m_lower.extends_down_to(value) and m_upper.extends_up_to(value);
316  }
317 
319 
322  constexpr bool contains(range<TYPE> const &other) const
323  noexcept(noexcept((*this & other) == other))
324  {
325  return (*this & other) == other;
326  }
327 
328  [[nodiscard]] constexpr range_bound<TYPE> const &
329  lower_bound() const & noexcept
330  {
331  return m_lower;
332  }
333  [[nodiscard]] constexpr range_bound<TYPE> const &
334  upper_bound() const & noexcept
335  {
336  return m_upper;
337  }
338 
340 
342  constexpr range operator&(range const &other) const
343  {
344  range_bound<TYPE> lower{no_bound{}};
345  if (not this->lower_bound().is_limited())
346  lower = other.lower_bound();
347  else if (not other.lower_bound().is_limited())
348  lower = this->lower_bound();
349  else if (*this->lower_bound().value() < *other.lower_bound().value())
350  lower = other.lower_bound();
351  else if (*other.lower_bound().value() < *this->lower_bound().value())
352  lower = this->lower_bound();
353  else if (this->lower_bound().is_exclusive())
354  lower = this->lower_bound();
355  else
356  lower = other.lower_bound();
357 
358  range_bound<TYPE> upper{no_bound{}};
359  if (not this->upper_bound().is_limited())
360  upper = other.upper_bound();
361  else if (not other.upper_bound().is_limited())
362  upper = this->upper_bound();
363  else if (*other.upper_bound().value() < *this->upper_bound().value())
364  upper = other.upper_bound();
365  else if (*this->upper_bound().value() < *other.upper_bound().value())
366  upper = this->upper_bound();
367  else if (this->upper_bound().is_exclusive())
368  upper = this->upper_bound();
369  else
370  upper = other.upper_bound();
371 
372  if (
373  lower.is_limited() and upper.is_limited() and
374  (*upper.value() < *lower.value()))
375  return {};
376  else
377  return {lower, upper};
378  }
379 
381  template<typename DEST> operator range<DEST>() const
382  {
383  range_bound<DEST> lower{no_bound{}}, upper{no_bound{}};
384  if (lower_bound().is_inclusive())
385  lower = inclusive_bound<DEST>{*lower_bound().value()};
386  else if (lower_bound().is_exclusive())
387  lower = exclusive_bound<DEST>{*lower_bound().value()};
388 
389  if (upper_bound().is_inclusive())
390  upper = inclusive_bound<DEST>{*upper_bound().value()};
391  else if (upper_bound().is_exclusive())
392  upper = exclusive_bound<DEST>{*upper_bound().value()};
393 
394  return {lower, upper};
395  }
396 };
397 
398 
400 
403 template<typename TYPE> struct string_traits<range<TYPE>>
404 {
405  [[nodiscard]] static inline zview
406  to_buf(char *begin, char *end, range<TYPE> const &value)
407  {
408  return generic_to_buf(begin, end, value);
409  }
410 
411  static inline char *
412  into_buf(char *begin, char *end, range<TYPE> const &value)
413  {
414  if (value.empty())
415  {
416  if ((end - begin) <= internal::ssize(s_empty))
417  throw conversion_overrun{s_overrun.c_str()};
418  char *here = begin + s_empty.copy(begin, std::size(s_empty));
419  *here++ = '\0';
420  return here;
421  }
422  else
423  {
424  if (end - begin < 4)
425  throw conversion_overrun{s_overrun.c_str()};
426  char *here = begin;
427  *here++ =
428  (static_cast<char>(value.lower_bound().is_inclusive() ? '[' : '('));
429  TYPE const *lower{value.lower_bound().value()};
430  // Convert bound (but go back to overwrite that trailing zero).
431  if (lower != nullptr)
432  here = string_traits<TYPE>::into_buf(here, end, *lower) - 1;
433  *here++ = ',';
434  TYPE const *upper{value.upper_bound().value()};
435  // Convert bound (but go back to overwrite that trailing zero).
436  if (upper != nullptr)
437  here = string_traits<TYPE>::into_buf(here, end, *upper) - 1;
438  if ((end - here) < 2)
439  throw conversion_overrun{s_overrun.c_str()};
440  *here++ =
441  static_cast<char>(value.upper_bound().is_inclusive() ? ']' : ')');
442  *here++ = '\0';
443  return here;
444  }
445  }
446 
447  [[nodiscard]] static inline range<TYPE> from_string(std::string_view text)
448  {
449  if (std::size(text) < 3)
450  throw pqxx::conversion_error{err_bad_input(text)};
451  bool left_inc{false};
452  switch (text[0])
453  {
454  case '[': left_inc = true; break;
455 
456  case '(': break;
457 
458  case 'e':
459  case 'E':
460  if (
461  (std::size(text) != std::size(s_empty)) or
462  (text[1] != 'm' and text[1] != 'M') or
463  (text[2] != 'p' and text[2] != 'P') or
464  (text[3] != 't' and text[3] != 'T') or
465  (text[4] != 'y' and text[4] != 'Y'))
466  throw pqxx::conversion_error{err_bad_input(text)};
467  return {};
468  break;
469 
470  default: throw pqxx::conversion_error{err_bad_input(text)};
471  }
472 
473  // The field parser uses this to track which field it's parsing, and
474  // when not to expect a field separator.
475  std::size_t index{0};
476  // The last field we expect to see.
477  static constexpr std::size_t last{1};
478  // Current parsing position. We skip the opening parenthesis or bracket.
479  std::size_t pos{1};
480  // The string may leave out either bound to indicate that it's unlimited.
481  std::optional<TYPE> lower, upper;
482  // We reuse the same field parser we use for composite values and arrays.
483  auto const field_parser{
484  pqxx::internal::specialize_parse_composite_field<std::optional<TYPE>>(
485  pqxx::internal::encoding_group::UTF8)};
486  field_parser(index, text, pos, lower, last);
487  field_parser(index, text, pos, upper, last);
488 
489  // We need one more character: the closing parenthesis or bracket.
490  if (pos != std::size(text))
491  throw pqxx::conversion_error{err_bad_input(text)};
492  char const closing{text[pos - 1]};
493  if (closing != ')' and closing != ']')
494  throw pqxx::conversion_error{err_bad_input(text)};
495  bool const right_inc{closing == ']'};
496 
497  range_bound<TYPE> lower_bound{no_bound{}}, upper_bound{no_bound{}};
498  if (lower)
499  {
500  if (left_inc)
501  lower_bound = inclusive_bound{*lower};
502  else
503  lower_bound = exclusive_bound{*lower};
504  }
505  if (upper)
506  {
507  if (right_inc)
508  upper_bound = inclusive_bound{*upper};
509  else
510  upper_bound = exclusive_bound{*upper};
511  }
512 
513  return {lower_bound, upper_bound};
514  }
515 
516  [[nodiscard]] static inline constexpr std::size_t
517  size_buffer(range<TYPE> const &value) noexcept
518  {
519  TYPE const *lower{value.lower_bound().value()},
520  *upper{value.upper_bound().value()};
521  std::size_t const lsz{
522  lower == nullptr ? 0 : string_traits<TYPE>::size_buffer(*lower) - 1},
523  usz{upper == nullptr ? 0 : string_traits<TYPE>::size_buffer(*upper) - 1};
524 
525  if (value.empty())
526  return std::size(s_empty) + 1;
527  else
528  return 1 + lsz + 1 + usz + 2;
529  }
530 
531 private:
532  static constexpr zview s_empty{"empty"_zv};
533  static constexpr auto s_overrun{"Not enough space in buffer for range."_zv};
534 
536  static std::string err_bad_input(std::string_view text)
537  {
538  return internal::concat("Invalid range input: '", text, "'");
539  }
540 };
541 
542 
544 template<typename TYPE> struct nullness<range<TYPE>> : no_null<range<TYPE>>
545 {};
546 } // namespace pqxx
547 #endif
constexpr bool is_inclusive() const noexcept
Is this boundary an inclusive one?
Definition: range.hxx:174
std::string concat(TYPE...item)
Efficiently combine a bunch of items into one big string.
Definition: concat.hxx:31
Marker-type wrapper: zero-terminated std::string_view.
Definition: zview.hxx:37
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:301
An unlimited boundary value to a pqxx::range.
Definition: range.hxx:23
auto ssize(T const &c)
Transitional: std::ssize(), or custom implementation if not available.
Definition: util.hxx:555
constexpr TYPE const * value() const &noexcept
Return bound value, or nullptr if it's not limited.
Definition: range.hxx:204
std::size_t size_buffer(TYPE const &...value) noexcept
Estimate how much buffer space is needed to represent values as a string.
Definition: strconv.hxx:526
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:108
constexpr bool is_limited() const noexcept
Is this a finite bound?
Definition: range.hxx:168
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:311
constexpr bool contains(range< TYPE > const &other) const noexcept(noexcept((*this &other)==other))
Does this range encompass all of other?
Definition: range.hxx:322
T from_string(field const &value)
Convert a field's value to type T.
Definition: field.hxx:548
constexpr range() noexcept(noexcept(exclusive_bound< TYPE >{TYPE{}}))
Create an empty range.
Definition: range.hxx:266
std::vector< std::string_view > to_buf(char *here, char const *end, TYPE...value)
Convert multiple values to strings inside a single buffer.
Definition: strconv.hxx:493
constexpr bool extends_down_to(TYPE const &value) const
Would this bound, as a lower bound, include value?
Definition: range.hxx:186
A C++ equivalent to PostgreSQL's range types.
Definition: range.hxx:239
constexpr bool is_null(TYPE const &value) noexcept
Is value null?
Definition: strconv.hxx:515
An exclusive boundary value to a pqxx::range.
Definition: range.hxx:81
Could not convert value to string: not enough buffer space.
Definition: except.hxx:312
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:62
constexpr range operator&(range const &other) const
Intersection of two ranges.
Definition: range.hxx:342
A range boundary value.
Definition: range.hxx:120
Value conversion failed, e.g. when converting "Hello" to int.
Definition: except.hxx:282
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:69
The home of all libpqxx classes, functions, templates, etc.
Definition: array.cxx:26
zview generic_to_buf(char *begin, char *end, TYPE const &value)
Implement string_traits::to_buf by calling into_buf.
Definition: strconv.hxx:587
Nullness traits describing a type which does not have a null value.
Definition: strconv.hxx:112
constexpr bool is_exclusive() const noexcept
Is this boundary an exclusive one?
Definition: range.hxx:180
Traits class for use in string conversions.
Definition: strconv.hxx:153
Invalid argument passed to libpqxx, similar to std::invalid_argument.
Definition: except.hxx:265
An inclusive boundary value to a pqxx::range.
Definition: range.hxx:42
Traits describing a type's "null value," if any.
Definition: strconv.hxx:90
constexpr range(range_bound< TYPE > lower, range_bound< TYPE > upper)
Create a range.
Definition: range.hxx:251
static char * into_buf(char *begin, char *end, TYPE const &value)
Write value's string representation into buffer at begin.
constexpr bool extends_up_to(TYPE const &value) const
Would this bound, as an upper bound, include value?
Definition: range.hxx:195
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:101
static std::size_t size_buffer(TYPE const &value) noexcept
Estimate how much buffer space is needed to represent value.