libpqxx 7.7.5
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 <variant>
9
10#include "pqxx/internal/array-composite.hxx"
11#include "pqxx/internal/concat.hxx"
12
13namespace pqxx
14{
16
23{
24 template<typename TYPE> constexpr bool extends_down_to(TYPE const &) const
25 {
26 return true;
27 }
28 template<typename TYPE> constexpr bool extends_up_to(TYPE const &) const
29 {
30 return true;
31 }
32};
33
34
36
39template<typename TYPE> class inclusive_bound
40{
41public:
42 inclusive_bound() = delete;
43 explicit inclusive_bound(TYPE const &value) : m_value{value}
44 {
45 if (is_null(value))
46 throw argument_error{"Got null value as an inclusive range bound."};
47 }
48
49 [[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; }
50
51 // TODO: constexpr and/or noexcept if underlying operator supports it.
53 [[nodiscard]] bool extends_down_to(TYPE const &value) const
54 {
55 return not(value < m_value);
56 }
57
58 // TODO: constexpr and/or noexcept if underlying operator supports it.
60 [[nodiscard]] bool extends_up_to(TYPE const &value) const
61 {
62 return not(m_value < value);
63 }
64
65private:
66 TYPE m_value;
67};
68
69
71
74template<typename TYPE> class exclusive_bound
75{
76public:
77 exclusive_bound() = delete;
78 explicit exclusive_bound(TYPE const &value) : m_value{value}
79 {
80 if (is_null(value))
81 throw argument_error{"Got null value as an exclusive range bound."};
82 }
83
84 [[nodiscard]] constexpr TYPE const &get() const &noexcept { return m_value; }
85
86 // TODO: constexpr and/or noexcept if underlying operator supports it.
88 [[nodiscard]] bool extends_down_to(TYPE const &value) const
89 {
90 return m_value < value;
91 }
92
93 // TODO: constexpr and/or noexcept if underlying operator supports it.
95 [[nodiscard]] bool extends_up_to(TYPE const &value) const
96 {
97 return value < m_value;
98 }
99
100private:
101 TYPE m_value;
102};
103
104
106
109template<typename TYPE> class range_bound
110{
111public:
112 range_bound() = delete;
113 // TODO: constexpr and/or noexcept if underlying constructor supports it.
114 range_bound(no_bound) : m_bound{} {}
115 // TODO: constexpr and/or noexcept if underlying constructor supports it.
116 range_bound(inclusive_bound<TYPE> const &bound) : m_bound{bound} {}
117 // TODO: constexpr and/or noexcept if underlying constructor supports it.
118 range_bound(exclusive_bound<TYPE> const &bound) : m_bound{bound} {}
119 // TODO: constexpr and/or noexcept if underlying constructor supports it.
120 range_bound(range_bound const &) = default;
121 // TODO: constexpr and/or noexcept if underlying constructor supports it.
123
124 // TODO: constexpr and/or noexcept if underlying operators support it.
125 bool operator==(range_bound const &rhs) const
126 {
127 if (this->is_limited())
128 return (
129 rhs.is_limited() and (this->is_inclusive() == rhs.is_inclusive()) and
130 (*this->value() == *rhs.value()));
131 else
132 return not rhs.is_limited();
133 }
134
135 // TODO: constexpr and/or noexcept if underlying operator supports it.
136 bool operator!=(range_bound const &rhs) const { return not(*this == rhs); }
137 range_bound &operator=(range_bound const &) = default;
139
141 constexpr bool is_limited() const noexcept
142 {
143 return not std::holds_alternative<no_bound>(m_bound);
144 }
145
147 constexpr bool is_inclusive() const noexcept
148 {
149 return std::holds_alternative<inclusive_bound<TYPE>>(m_bound);
150 }
151
153 constexpr bool is_exclusive() const noexcept
154 {
155 return std::holds_alternative<exclusive_bound<TYPE>>(m_bound);
156 }
157
158 // TODO: constexpr/noexcept if underlying function supports it.
160 bool extends_down_to(TYPE const &value) const
161 {
162 return std::visit(
163 [&value](auto const &bound) { return bound.extends_down_to(value); },
164 m_bound);
165 }
166
167 // TODO: constexpr/noexcept if underlying function supports it.
169 bool extends_up_to(TYPE const &value) const
170 {
171 return std::visit(
172 [&value](auto const &bound) { return bound.extends_up_to(value); },
173 m_bound);
174 }
175
177 [[nodiscard]] constexpr TYPE const *value() const &noexcept
178 {
179 return std::visit(
180 [](auto const &bound) noexcept {
181 using bound_t = std::decay_t<decltype(bound)>;
182 if constexpr (std::is_same_v<bound_t, no_bound>)
183 return static_cast<TYPE const *>(nullptr);
184 else
185 return &bound.get();
186 },
187 m_bound);
188 }
189
190private:
191 std::variant<no_bound, inclusive_bound<TYPE>, exclusive_bound<TYPE>> m_bound;
192};
193
194
195// C++20: Concepts for comparisons, construction, etc.
197
215template<typename TYPE> class range
216{
217public:
219
224 m_lower{lower}, m_upper{upper}
225 {
226 if (
227 lower.is_limited() and upper.is_limited() and
228 (*upper.value() < *lower.value()))
229 throw range_error{internal::concat(
230 "Range's lower bound (", *lower.value(),
231 ") is greater than its upper bound (", *upper.value(), ").")};
232 }
233
234 // TODO: constexpr and/or noexcept if underlying constructor supports it.
236
240 m_lower{exclusive_bound<TYPE>{TYPE{}}},
241 m_upper{exclusive_bound<TYPE>{TYPE{}}}
242 {}
243
244 // TODO: constexpr and/or noexcept if underlying operators support it.
245 bool operator==(range const &rhs) const
246 {
247 return (this->lower_bound() == rhs.lower_bound() and
248 this->upper_bound() == rhs.upper_bound()) or
249 (this->empty() and rhs.empty());
250 }
251
252 // TODO: constexpr and/or noexcept if underlying operator supports it.
253 bool operator!=(range const &rhs) const { return !(*this == rhs); }
254
255 range(range const &) = default;
256 range(range &&) = default;
257 range &operator=(range const &) = default;
258 range &operator=(range &&) = default;
259
260 // TODO: constexpr and/or noexcept if underlying operator supports it.
262
270 bool empty() const
271 {
272 return (m_lower.is_exclusive() or m_upper.is_exclusive()) and
273 m_lower.is_limited() and m_upper.is_limited() and
274 not(*m_lower.value() < *m_upper.value());
275 }
276
277 // TODO: constexpr and/or noexcept if underlying functions support it.
279 bool contains(TYPE value) const
280 {
281 return m_lower.extends_down_to(value) and m_upper.extends_up_to(value);
282 }
283
284 // TODO: constexpr and/or noexcept if underlying operators support it.
286
289 bool contains(range<TYPE> const &other) const
290 {
291 return (*this & other) == other;
292 }
293
294 [[nodiscard]] constexpr range_bound<TYPE> const &
295 lower_bound() const &noexcept
296 {
297 return m_lower;
298 }
299 [[nodiscard]] constexpr range_bound<TYPE> const &
300 upper_bound() const &noexcept
301 {
302 return m_upper;
303 }
304
305 // TODO: constexpr and/or noexcept if underlying operators support it.
307
309 range operator&(range const &other) const
310 {
312 if (not this->lower_bound().is_limited())
313 lower = other.lower_bound();
314 else if (not other.lower_bound().is_limited())
315 lower = this->lower_bound();
316 else if (*this->lower_bound().value() < *other.lower_bound().value())
317 lower = other.lower_bound();
318 else if (*other.lower_bound().value() < *this->lower_bound().value())
319 lower = this->lower_bound();
320 else if (this->lower_bound().is_exclusive())
321 lower = this->lower_bound();
322 else
323 lower = other.lower_bound();
324
326 if (not this->upper_bound().is_limited())
327 upper = other.upper_bound();
328 else if (not other.upper_bound().is_limited())
329 upper = this->upper_bound();
330 else if (*other.upper_bound().value() < *this->upper_bound().value())
331 upper = other.upper_bound();
332 else if (*this->upper_bound().value() < *other.upper_bound().value())
333 upper = this->upper_bound();
334 else if (this->upper_bound().is_exclusive())
335 upper = this->upper_bound();
336 else
337 upper = other.upper_bound();
338
339 if (
340 lower.is_limited() and upper.is_limited() and
341 (*upper.value() < *lower.value()))
342 return {};
343 else
344 return {lower, upper};
345 }
346
348 template<typename DEST> operator range<DEST>() const
349 {
350 range_bound<DEST> lower{no_bound{}}, upper{no_bound{}};
351 if (lower_bound().is_inclusive())
352 lower = inclusive_bound<DEST>{*lower_bound().value()};
353 else if (lower_bound().is_exclusive())
354 lower = exclusive_bound<DEST>{*lower_bound().value()};
355
356 if (upper_bound().is_inclusive())
357 upper = inclusive_bound<DEST>{*upper_bound().value()};
358 else if (upper_bound().is_exclusive())
359 upper = exclusive_bound<DEST>{*upper_bound().value()};
360
361 return {lower, upper};
362 }
363
364private:
365 range_bound<TYPE> m_lower, m_upper;
366};
367
368
370
373template<typename TYPE> struct string_traits<range<TYPE>>
374{
375 [[nodiscard]] static inline zview
376 to_buf(char *begin, char *end, range<TYPE> const &value)
377 {
378 return generic_to_buf(begin, end, value);
379 }
380
381 static inline char *
382 into_buf(char *begin, char *end, range<TYPE> const &value)
383 {
384 if (value.empty())
385 {
386 if ((end - begin) <= internal::ssize(s_empty))
387 throw conversion_overrun{s_overrun.c_str()};
388 char *here = begin + s_empty.copy(begin, std::size(s_empty));
389 *here++ = '\0';
390 return here;
391 }
392 else
393 {
394 if (end - begin < 4)
395 throw conversion_overrun{s_overrun.c_str()};
396 char *here = begin;
397 *here++ =
398 (static_cast<char>(value.lower_bound().is_inclusive() ? '[' : '('));
399 TYPE const *lower{value.lower_bound().value()};
400 // Convert bound (but go back to overwrite that trailing zero).
401 if (lower != nullptr)
402 here = string_traits<TYPE>::into_buf(here, end, *lower) - 1;
403 *here++ = ',';
404 TYPE const *upper{value.upper_bound().value()};
405 // Convert bound (but go back to overwrite that trailing zero).
406 if (upper != nullptr)
407 here = string_traits<TYPE>::into_buf(here, end, *upper) - 1;
408 if ((end - here) < 2)
409 throw conversion_overrun{s_overrun.c_str()};
410 *here++ =
411 static_cast<char>(value.upper_bound().is_inclusive() ? ']' : ')');
412 *here++ = '\0';
413 return here;
414 }
415 }
416
417 [[nodiscard]] static inline range<TYPE> from_string(std::string_view text)
418 {
419 if (std::size(text) < 3)
420 throw pqxx::conversion_error{err_bad_input(text)};
421 bool left_inc{false};
422 switch (text[0])
423 {
424 case '[': left_inc = true; break;
425
426 case '(': break;
427
428 case 'e':
429 case 'E':
430 if (
431 (std::size(text) != std::size(s_empty)) or
432 (text[1] != 'm' and text[1] != 'M') or
433 (text[2] != 'p' and text[2] != 'P') or
434 (text[3] != 't' and text[3] != 'T') or
435 (text[4] != 'y' and text[4] != 'Y'))
436 throw pqxx::conversion_error{err_bad_input(text)};
437 return {};
438 break;
439
440 default: throw pqxx::conversion_error{err_bad_input(text)};
441 }
442
443 auto scan{internal::get_glyph_scanner(internal::encoding_group::UTF8)};
444 // The field parser uses this to track which field it's parsing, and
445 // when not to expect a field separator.
446 std::size_t index{0};
447 // The last field we expect to see.
448 static constexpr std::size_t last{1};
449 // Current parsing position. We skip the opening parenthesis or bracket.
450 std::size_t pos{1};
451 // The string may leave out either bound to indicate that it's unlimited.
452 std::optional<TYPE> lower, upper;
453 // We reuse the same field parser we use for composite values and arrays.
454 internal::parse_composite_field(index, text, pos, lower, scan, last);
455 internal::parse_composite_field(index, text, pos, upper, scan, last);
456
457 // We need one more character: the closing parenthesis or bracket.
458 if (pos != std::size(text))
459 throw pqxx::conversion_error{err_bad_input(text)};
460 char const closing{text[pos - 1]};
461 if (closing != ')' and closing != ']')
462 throw pqxx::conversion_error{err_bad_input(text)};
463 bool const right_inc{closing == ']'};
464
465 range_bound<TYPE> lower_bound{no_bound{}}, upper_bound{no_bound{}};
466 if (lower)
467 {
468 if (left_inc)
469 lower_bound = inclusive_bound{*lower};
470 else
471 lower_bound = exclusive_bound{*lower};
472 }
473 if (upper)
474 {
475 if (right_inc)
476 upper_bound = inclusive_bound{*upper};
477 else
478 upper_bound = exclusive_bound{*upper};
479 }
480
481 return {lower_bound, upper_bound};
482 }
483
484 [[nodiscard]] static inline constexpr std::size_t
485 size_buffer(range<TYPE> const &value) noexcept
486 {
487 TYPE const *lower{value.lower_bound().value()},
488 *upper{value.upper_bound().value()};
489 std::size_t const lsz{
490 lower == nullptr ? 0 : string_traits<TYPE>::size_buffer(*lower) - 1},
491 usz{upper == nullptr ? 0 : string_traits<TYPE>::size_buffer(*upper) - 1};
492
493 if (value.empty())
494 return std::size(s_empty) + 1;
495 else
496 return 1 + lsz + 1 + usz + 2;
497 }
498
499private:
500 static constexpr zview s_empty{"empty"_zv};
501 static constexpr auto s_overrun{"Not enough space in buffer for range."_zv};
502
504 static std::string err_bad_input(std::string_view text)
505 {
506 return internal::concat("Invalid range input: '", text, "'");
507 }
508};
509
510
512template<typename TYPE> struct nullness<range<TYPE>> : no_null<range<TYPE>>
513{};
514} // namespace pqxx
515#endif
The home of all libpqxx classes, functions, templates, etc.
Definition array.hxx:27
constexpr bool is_null(TYPE const &value) noexcept
Is value null?
Definition strconv.hxx:370
zview generic_to_buf(char *begin, char *end, TYPE const &value)
Implement string_traits<TYPE>::to_buf by calling into_buf.
Definition strconv.hxx:442
Invalid argument passed to libpqxx, similar to std::invalid_argument.
Definition except.hxx:181
Value conversion failed, e.g. when converting "Hello" to int.
Definition except.hxx:188
Could not convert value to string: not enough buffer space.
Definition except.hxx:195
Something is out of range, similar to std::out_of_range.
Definition except.hxx:202
An unlimited boundary value to a pqxx::range.
Definition range.hxx:23
constexpr bool extends_down_to(TYPE const &) const
Definition range.hxx:24
constexpr bool extends_up_to(TYPE const &) const
Definition range.hxx:28
An inclusive boundary value to a pqxx::range.
Definition range.hxx:40
bool extends_up_to(TYPE const &value) const
Would this bound, as an upper bound, include value?
Definition range.hxx:60
bool extends_down_to(TYPE const &value) const
Would this bound, as a lower bound, include value?
Definition range.hxx:53
constexpr TYPE const & get() const &noexcept
Definition range.hxx:49
inclusive_bound(TYPE const &value)
Definition range.hxx:43
An exclusive boundary value to a pqxx::range.
Definition range.hxx:75
exclusive_bound(TYPE const &value)
Definition range.hxx:78
bool extends_down_to(TYPE const &value) const
Would this bound, as a lower bound, include value?
Definition range.hxx:88
constexpr TYPE const & get() const &noexcept
Definition range.hxx:84
bool extends_up_to(TYPE const &value) const
Would this bound, as an upper bound, include value?
Definition range.hxx:95
A range boundary value.
Definition range.hxx:110
bool extends_down_to(TYPE const &value) const
Would this bound, as a lower bound, include value?
Definition range.hxx:160
range_bound(range_bound &&)=default
constexpr bool is_exclusive() const noexcept
Is this boundary an exclusive one?
Definition range.hxx:153
bool extends_up_to(TYPE const &value) const
Would this bound, as an upper bound, include value?
Definition range.hxx:169
constexpr bool is_limited() const noexcept
Is this a finite bound?
Definition range.hxx:141
constexpr TYPE const * value() const &noexcept
Return bound value, or nullptr if it's not limited.
Definition range.hxx:177
range_bound(inclusive_bound< TYPE > const &bound)
Definition range.hxx:116
range_bound()=delete
range_bound(range_bound const &)=default
range_bound & operator=(range_bound const &)=default
range_bound(exclusive_bound< TYPE > const &bound)
Definition range.hxx:118
range_bound(no_bound)
Definition range.hxx:114
constexpr bool is_inclusive() const noexcept
Is this boundary an inclusive one?
Definition range.hxx:147
bool operator==(range_bound const &rhs) const
Definition range.hxx:125
range_bound & operator=(range_bound &&)=default
bool operator!=(range_bound const &rhs) const
Definition range.hxx:136
A C++ equivalent to PostgreSQL's range types.
Definition range.hxx:216
range(range_bound< TYPE > lower, range_bound< TYPE > upper)
Create a range.
Definition range.hxx:223
range(range &&)=default
range(range const &)=default
range & operator=(range &&)=default
range & operator=(range const &)=default
constexpr range_bound< TYPE > const & upper_bound() const &noexcept
Definition range.hxx:300
bool contains(range< TYPE > const &other) const
Does this range encompass all of other?
Definition range.hxx:289
bool operator==(range const &rhs) const
Definition range.hxx:245
bool empty() const
Is this range clearly empty?
Definition range.hxx:270
bool contains(TYPE value) const
Does this range encompass value?
Definition range.hxx:279
range()
Create an empty range.
Definition range.hxx:239
bool operator!=(range const &rhs) const
Definition range.hxx:253
range operator&(range const &other) const
Intersection of two ranges.
Definition range.hxx:309
constexpr range_bound< TYPE > const & lower_bound() const &noexcept
Definition range.hxx:295
static constexpr std::size_t size_buffer(range< TYPE > const &value) noexcept
Definition range.hxx:485
static char * into_buf(char *begin, char *end, range< TYPE > const &value)
Definition range.hxx:382
static zview to_buf(char *begin, char *end, range< TYPE > const &value)
Definition range.hxx:376
static range< TYPE > from_string(std::string_view text)
Definition range.hxx:417
Traits describing a type's "null value," if any.
Definition strconv.hxx:93
Nullness traits describing a type which does not have a null value.
Definition strconv.hxx:115
Traits class for use in string conversions.
Definition strconv.hxx:155
Marker-type wrapper: zero-terminated std::string_view.
Definition zview.hxx:38