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