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