userver: userver/storages/postgres/io/range_types.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
range_types.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/io/range_types.hpp
4/// @brief Ranges I/O support
5/// @ingroup userver_postgres_parse_and_format
6
7/// @page pg_range_types uPg: Range types
8///
9/// PostgreSQL provides a facility to represent intervals of values. They can be
10/// bounded (have both ends), e.g [0, 1], [2, 10), unbounded (at least one end
11/// is infinity or absent), e.g. (-∞, +∞), and empty.
12///
13/// The range that can be unbounded is modelled by storages::postgres::Range<>
14/// template, which provides means of constructing any possible combination of
15/// interval ends. It is very versatile, but not very convenient in most cases.
16/// storages::postgres::BoundedRange<> template is an utility that works only
17/// with bounded ranges.
18///
19/// @par PostgreSQL Built-in Range Datatypes
20///
21/// Some of PostgreSQL built-in range datatypes
22/// (https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-BUILTIN)
23/// are supported by the library, please see @ref pg_types
24///
25/// @par User Range Types
26///
27/// A user can define custom range types
28/// (https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-DEFINING)
29/// and they can be mapped to C++ counterparts, e.g.
30///
31/// @code
32/// CREATE TYPE my_range AS RANGE (
33/// subtype = my_type
34/// );
35/// @endcode
36///
37/// and declare a mapping from C++ range to this type just as for any other user
38/// type:
39///
40/// @code
41/// template<>
42/// struct CppToUserPg<Range<my_type>> {
43/// static constexpr DBTypeName postgres_name = "my_type";
44/// };
45/// @endcode
46///
47/// Please note that `my_type` must be comparable both in Postgres and in C++
48///
49/// See @ref pg_user_types for more info
50///
51/// @par Time Range and Other Widely Used Types
52///
53/// If you need a range of PostgreSQL `float` type or `time` type (actually any
54/// type mapped to C++ type that is highly likely used by other developers),
55/// please DO NOT specialize mapping for `Range<float>`, `Range<double>` or
56/// `Range<TimeOfDay<seconds>>`. Please declare a strong typedef for your range
57/// or bounded range and map it to your postgres range type.
58///
59/// Here is an example how to define a strong typedef for a range of TimeOfDay
60/// and map it to postgres user range type.
61///
62/// @snippet storages/postgres/tests/user_types_pgtest.cpp Time range
63///
64/// @snippet storages/postgres/tests/user_types_pgtest.cpp Range type mapping
65///
66///
67/// ----------
68///
69/// @htmlonly <div class="bottom-nav"> @endhtmlonly
70/// ⇦ @ref pg_enum | @ref pg_arrays ⇨
71/// @htmlonly </div> @endhtmlonly
72
73#include <optional>
74#include <ostream>
75
76#include <fmt/format.h>
77
78#include <userver/storages/postgres/exceptions.hpp>
79#include <userver/storages/postgres/io/buffer_io_base.hpp>
80#include <userver/storages/postgres/io/field_buffer.hpp>
81#include <userver/storages/postgres/io/traits.hpp>
82#include <userver/storages/postgres/io/type_mapping.hpp>
83#include <userver/storages/postgres/io/type_traits.hpp>
84#include <userver/storages/postgres/io/user_types.hpp>
85
86#include <userver/utils/assert.hpp>
87#include <userver/utils/flags.hpp>
88
89USERVER_NAMESPACE_BEGIN
90
91namespace storages::postgres {
92
93struct UnboundedType {};
94constexpr UnboundedType kUnbounded{};
95
96enum class RangeBound {
97 kNone = 0x00,
98 kLower = 0x01,
99 kUpper = 0x02,
100 kBoth = kLower | kUpper,
101};
102
103using RangeBounds = USERVER_NAMESPACE::utils::Flags<RangeBound>;
104
105template <typename T>
106class Range {
107 static constexpr bool kNothrowValueCtor =
108 std::is_nothrow_default_constructible_v<T>;
109 static constexpr bool kNothrowValueCopy =
110 std::is_nothrow_copy_constructible_v<T>;
111 static constexpr bool kNothrowValueMove =
112 std::is_nothrow_move_constructible_v<T>;
113 static constexpr bool kIsDiscreteValue = std::is_integral_v<T>;
114
115 public:
116 using OptionalValue = std::optional<T>;
117
118 /// Empty range
119 Range() = default;
120
121 /// Unbounded range
122 Range(UnboundedType, UnboundedType) noexcept : data{RangeData{}} {}
123
124 /// Bounded range
125 template <typename U, typename = std::enable_if_t<
127 Range(U&& lower, U&& upper, RangeBounds bounds = RangeBound::kLower);
128
129 /// Range with a lower bound
130 template <typename U, typename = std::enable_if_t<
132 Range(U&& lower, UnboundedType ub,
133 RangeBounds bounds = RangeBound::kLower) noexcept(kNothrowValueCopy);
134
135 /// Range with an upper bound
136 template <typename U, typename = std::enable_if_t<
138 Range(UnboundedType ub, U&& upper,
139 RangeBounds bounds = RangeBound::kNone) noexcept(kNothrowValueCopy);
140
141 Range(const OptionalValue& lower, const OptionalValue& upper,
142 RangeBounds bounds);
143
144 /// Convert from a range of different type.
145 ///
146 /// Intentionally implicit
147 template <typename U,
148 typename = std::enable_if_t<std::is_convertible_v<U, T>>>
149 Range(const Range<U>& rhs);
150
151 bool operator==(const Range& rhs) const;
152
153 bool operator!=(const Range& rhs) const { return !(*this == rhs); }
154
155 bool Empty() const { return !data; }
156
157 /// Make the range empty
158 void Clear() { data.reset(); }
159
160 bool HasLowerBound() const {
161 return !!data && data->HasBound(RangeBound::kLower);
162 }
163 bool HasUpperBound() const {
164 return !!data && data->HasBound(RangeBound::kUpper);
165 }
166
167 /// Get the lower bound.
168 const OptionalValue& GetLowerBound() const {
169 if (!!data) {
170 return data->GetOptionalValue(RangeBound::kLower);
171 }
172 return kNoValue;
173 }
174
175 /// Get the upper bound.
176 const OptionalValue& GetUpperBound() const {
177 if (!!data) {
178 return data->GetOptionalValue(RangeBound::kUpper);
179 }
180 return kNoValue;
181 }
182
183 bool IsLowerBoundIncluded() const {
184 return !!data && data->IsBoundIncluded(RangeBound::kLower);
185 }
186 bool IsUpperBoundIncluded() const {
187 return !!data && data->IsBoundIncluded(RangeBound::kUpper);
188 }
189
190 private:
191 template <typename U>
192 friend class Range;
193
194 struct RangeData {
195 // Unbounded range
196 RangeData() noexcept = default;
197
198 template <typename U>
199 RangeData(U&& lower, U&& upper, RangeBounds bounds)
200 : RangeData{OptionalValue{std::forward<U>(lower)},
201 OptionalValue{std::forward<U>(upper)}, bounds} {}
202
203 template <typename U>
204 RangeData(U&& lower, UnboundedType,
205 RangeBounds bounds) noexcept(kNothrowValueCopy)
206 : RangeData{OptionalValue{std::forward<U>(lower)}, OptionalValue{},
207 bounds} {}
208
209 template <typename U>
210 RangeData(UnboundedType, U&& upper,
211 RangeBounds bounds) noexcept(kNothrowValueCopy)
212 : RangeData{OptionalValue{}, OptionalValue{std::forward<U>(upper)},
213 bounds} {}
214
215 RangeData(OptionalValue low, OptionalValue up, RangeBounds bounds)
216 : bounds{bounds}, lower{std::move(low)}, upper{std::move(up)} {
217 if (lower && upper && *upper < *lower) {
218 throw LogicError("Range lower bound is greater than upper");
219 }
220 }
221
222 bool operator==(const RangeData& rhs) const {
223 return BoundEqual(rhs, RangeBound::kLower) &&
224 BoundEqual(rhs, RangeBound::kUpper);
225 }
226
227 bool operator!=(const RangeData& rhs) const { return !(*this == rhs); }
228
229 bool HasBound(RangeBounds side) const;
230
231 bool IsBoundIncluded(RangeBounds side) const {
232 return HasBound(side) && (bounds & side);
233 }
234
235 bool BoundEqual(const RangeData& rhs, RangeBounds side) const;
236
237 // Using this function without checking is ub
238 const T& GetBoundValue(RangeBounds side) const {
239 if (side == RangeBound::kLower) return *lower;
240 UASSERT_MSG(side == RangeBound::kUpper,
241 "Invalid bounds side argument value");
242 return *upper;
243 }
244
245 const OptionalValue& GetOptionalValue(RangeBounds side) const {
246 if (side == RangeBound::kLower) return lower;
247 UASSERT_MSG(side == RangeBound::kUpper,
248 "Invalid bounds side argument value");
249 return upper;
250 }
251
252 RangeBounds bounds = RangeBound::kNone;
253 OptionalValue lower;
254 OptionalValue upper;
255 };
256
257 template <typename U>
258 static OptionalValue ConvertBound(const std::optional<U>& rhs) {
259 if (!rhs) return OptionalValue{};
260 return OptionalValue{*rhs};
261 }
262
263 template <typename U>
264 static std::optional<RangeData> ConvertData(const Range<U>& rhs) {
265 if (!rhs.data) return {};
266 return RangeData{ConvertBound(rhs.data->lower),
267 ConvertBound(rhs.data->upper), rhs.data->bounds};
268 }
269
270 std::optional<RangeData> data;
271
272 static const inline OptionalValue kNoValue{};
273};
274
275template <typename T>
276auto MakeRange(T&& lower, T&& upper, RangeBounds bounds = RangeBound::kLower) {
277 using ElementType = std::decay_t<T>;
278 return Range<ElementType>{std::forward<T>(lower), std::forward<T>(upper),
279 bounds};
280}
281
282template <typename T>
283auto MakeRange(T&& lower, UnboundedType,
284 RangeBounds bounds = RangeBound::kLower) {
285 using ElementType = std::decay_t<T>;
286 return Range<ElementType>{std::forward<T>(lower), kUnbounded, bounds};
287}
288
289template <typename T>
290auto MakeRange(UnboundedType, T&& upper,
291 RangeBounds bounds = RangeBound::kNone) {
292 using ElementType = std::decay_t<T>;
293 return Range<ElementType>{kUnbounded, std::forward<T>(upper), bounds};
294}
295
296using IntegerRange = Range<Integer>;
297using BigintRange = Range<Bigint>;
298
299template <typename T>
301 static constexpr bool kNothrowValueCtor =
302 std::is_nothrow_default_constructible_v<T>;
303
304 public:
305 using ValueType = T;
306
307 BoundedRange() noexcept(kNothrowValueCtor);
308
309 template <typename U, typename = std::enable_if_t<
310 std::is_convertible_v<std::decay_t<U>, T>>>
311 BoundedRange(U&& lower, U&& upper, RangeBounds bounds = RangeBound::kLower);
312
313 template <typename U>
314 explicit BoundedRange(Range<U>&&);
315
316 bool operator==(const BoundedRange& rhs) const;
317 bool operator!=(const BoundedRange& rhs) const { return !(*this == rhs); }
318
319 const ValueType& GetLowerBound() const { return *value_.GetLowerBound(); }
320 bool IsLowerBoundIncluded() const { return value_.IsLowerBoundIncluded(); }
321
322 const ValueType& GetUpperBound() const { return *value_.GetUpperBound(); }
323 bool IsUpperBoundIncluded() const { return value_.IsUpperBoundIncluded(); }
324
325 const Range<T>& GetUnboundedRange() const { return value_; }
326
327 // TODO Intersection and containment test functions on user demand
328 private:
329 Range<T> value_;
330};
331
332using BoundedIntegerRange = BoundedRange<Integer>;
333using BoundedBigintRange = BoundedRange<Bigint>;
334
335} // namespace storages::postgres
336
337namespace storages::postgres::io {
338
339namespace detail {
340
341enum class RangeFlag {
342 kNone = 0x00,
343 kEmpty = 0x01,
344 kLowerBoundInclusive = 0x02,
345 kUpperBoundInclusive = 0x04,
346 kLowerBoundInfinity = 0x08,
347 kUpperBoundInfinity = 0x10,
348 kLowerBoundNull = 0x20,
349 kUpperBoundNull = 0x40,
350 kContainEmpty = 0x80,
351};
352
353using RangeFlags = USERVER_NAMESPACE::utils::Flags<RangeFlag>;
354
355constexpr bool HasLowerBound(RangeFlags flags) {
356 return !(flags & RangeFlags{RangeFlag::kEmpty, RangeFlag::kLowerBoundNull,
357 RangeFlag::kLowerBoundInfinity});
358}
359
360constexpr bool HasUpperBound(RangeFlags flags) {
361 return !(flags & RangeFlags{RangeFlag::kEmpty, RangeFlag::kUpperBoundNull,
362 RangeFlag::kUpperBoundInfinity});
363}
364
365template <typename T>
366struct RangeBinaryParser : BufferParserBase<Range<T>> {
367 using BaseType = BufferParserBase<Range<T>>;
368 using ValueType = typename BaseType::ValueType;
369 using ElementType = T;
370 using ElementParser = typename traits::IO<ElementType>::ParserType;
371
372 static constexpr BufferCategory element_buffer_category =
373 traits::kParserBufferCategory<ElementParser>;
374
375 using BaseType::BaseType;
376
377 void operator()(FieldBuffer buffer, const TypeBufferCategory& categories) {
378 char wire_range_flags{0};
379
380 buffer.Read(wire_range_flags, BufferCategory::kPlainBuffer);
381 RangeFlags range_flags(static_cast<RangeFlag>(wire_range_flags));
382
383 ValueType wire_value;
384 if (range_flags != RangeFlag::kEmpty) {
385 RangeBounds bounds = RangeBound::kNone;
386 typename ValueType::OptionalValue lower;
387 typename ValueType::OptionalValue upper;
388 if (HasLowerBound(range_flags)) {
389 if (range_flags & RangeFlag::kLowerBoundInclusive) {
390 bounds |= RangeBound::kLower;
391 }
392 T tmp;
393 buffer.ReadRaw(tmp, categories, element_buffer_category);
394 lower = tmp;
395 }
396 if (HasUpperBound(range_flags)) {
397 if (range_flags & RangeFlag::kUpperBoundInclusive) {
398 bounds |= RangeBound::kUpper;
399 }
400 T tmp;
401 buffer.ReadRaw(tmp, categories, element_buffer_category);
402 upper = tmp;
403 }
404 wire_value = ValueType{lower, upper, bounds};
405 }
406 this->value = wire_value;
407 }
408};
409
410template <typename T>
411struct RangeBinaryFormatter : BufferFormatterBase<Range<T>> {
412 using BaseType = BufferFormatterBase<Range<T>>;
413
414 using BaseType::BaseType;
415
416 template <typename Buffer>
417 void operator()(const UserTypes& types, Buffer& buffer) const {
418 RangeFlags range_flags;
419 if (this->value.Empty()) {
420 range_flags |= RangeFlag::kEmpty;
421 } else {
422 // Mark lower/upper bound
423 if (!this->value.HasLowerBound()) {
424 range_flags |= RangeFlag::kLowerBoundInfinity;
425 } else if (this->value.IsLowerBoundIncluded()) {
426 range_flags |= RangeFlag::kLowerBoundInclusive;
427 }
428 if (!this->value.HasUpperBound()) {
429 range_flags |= RangeFlag::kUpperBoundInfinity;
430 } else if (this->value.IsUpperBoundIncluded()) {
431 range_flags |= RangeFlag::kUpperBoundInclusive;
432 }
433 }
434 char wire_range_flags = static_cast<char>(range_flags.GetValue());
435 io::WriteBuffer(types, buffer, wire_range_flags);
436 if (!this->value.Empty()) {
437 // Write lower/upper bounds
438 if (this->value.HasLowerBound()) {
439 io::WriteRawBinary(types, buffer, this->value.GetLowerBound());
440 }
441 if (this->value.HasUpperBound()) {
442 io::WriteRawBinary(types, buffer, this->value.GetUpperBound());
443 }
444 }
445 }
446};
447
448template <typename T>
449struct BoundedRangeBinaryParser : BufferParserBase<BoundedRange<T>> {
450 using BaseType = BufferParserBase<BoundedRange<T>>;
451 using ValueType = typename BaseType::ValueType;
452
453 using BaseType::BaseType;
454
455 void operator()(FieldBuffer buffer, const TypeBufferCategory& categories) {
456 Range<T> tmp;
457 io::ReadBuffer(buffer, tmp, categories);
458 this->value = ValueType{std::move(tmp)};
459 }
460};
461
462template <typename T>
463struct BoundedRangeBinaryFormatter : BufferFormatterBase<BoundedRange<T>> {
464 using BaseType = BufferFormatterBase<BoundedRange<T>>;
465
466 using BaseType::BaseType;
467
468 template <typename Buffer>
469 void operator()(const UserTypes& types, Buffer& buffer) const {
470 io::WriteBuffer(types, buffer, this->value.GetUnboundedRange());
471 }
472};
473
474} // namespace detail
475
476namespace traits {
477
478template <typename T>
480 using type = io::detail::RangeBinaryParser<T>;
481};
482
483template <typename T>
484struct ParserBufferCategory<io::detail::RangeBinaryParser<T>>
486
487template <typename T>
490};
491
492template <typename T>
495};
496
497template <typename T>
498struct ParserBufferCategory<io::detail::BoundedRangeBinaryParser<T>>
500
501template <typename T>
504};
505
506} // namespace traits
507
508template <>
509struct CppToSystemPg<IntegerRange> : PredefinedOid<PredefinedOids::kInt4Range> {
510};
511template <>
512struct CppToSystemPg<BoundedIntegerRange>
513 : PredefinedOid<PredefinedOids::kInt4Range> {};
514template <>
515struct CppToSystemPg<BigintRange> : PredefinedOid<PredefinedOids::kInt8Range> {
516};
517template <>
518struct CppToSystemPg<BoundedBigintRange>
519 : PredefinedOid<PredefinedOids::kInt8Range> {};
520
521} // namespace storages::postgres::io
522
523namespace storages::postgres {
524
525template <typename T>
526template <typename U, typename>
527Range<T>::Range(U&& lower, U&& upper, RangeBounds bounds)
529 if (lower == upper && bounds != RangeBound::kBoth) {
530 // this will make an empty range
531 data.reset();
532 }
533}
534
535template <typename T>
536template <typename U, typename>
537Range<T>::Range(U&& lower, UnboundedType ub,
538 RangeBounds bounds) noexcept(kNothrowValueCopy)
540
541template <typename T>
542template <typename U, typename>
543Range<T>::Range(UnboundedType ub, U&& upper,
544 RangeBounds bounds) noexcept(kNothrowValueCopy)
546
547template <typename T>
548Range<T>::Range(const OptionalValue& lower, const OptionalValue& upper,
549 RangeBounds bounds)
550 : data{RangeData{lower, upper, bounds}} {}
551
552template <typename T>
553template <typename U, typename>
554Range<T>::Range(const Range<U>& rhs) : data{ConvertData(rhs)} {}
555
556template <typename T>
557bool Range<T>::operator==(const Range& rhs) const {
558 return (Empty() && rhs.Empty()) || (data == rhs.data);
559}
560
561template <typename T>
562std::ostream& operator<<(std::ostream& os, const Range<T>& val) {
563 if (val.Empty()) return os << "empty";
564 if (val.HasLowerBound() && val.IsLowerBoundIncluded())
565 os << '[';
566 else
567 os << '(';
568 if (val.HasLowerBound())
569 os << *val.GetLowerBound();
570 else
571 os << "-inf";
572 os << ", ";
573 if (val.HasUpperBound())
574 os << *val.GetUpperBound();
575 else
576 os << "inf";
577 if (val.HasUpperBound() && val.IsUpperBoundIncluded())
578 os << ']';
579 else
580 os << ')';
581 return os;
582}
583
584template <typename T>
585bool Range<T>::RangeData::HasBound(RangeBounds side) const {
586 if (side == RangeBound::kLower) {
587 return !!lower;
588 }
589 if (side == RangeBound::kUpper) {
590 return !!upper;
591 }
592 return false;
593}
594
595template <typename T>
596bool Range<T>::RangeData::BoundEqual(const RangeData& rhs,
597 RangeBounds side) const {
598 bool has_bound = HasBound(side);
599 if (has_bound != rhs.HasBound(side)) {
600 return false;
601 }
602 if (!has_bound) { // both are unbounded
603 return true;
604 }
605 const auto& lval = GetBoundValue(side);
606 const auto& rval = rhs.GetBoundValue(side);
607 if ((bounds & side) == (rhs.bounds & side)) {
608 // same include
609 return lval == rval;
610 }
611 if constexpr (kIsDiscreteValue) {
612 T diff = (side == RangeBound::kLower ? 1 : -1);
613 if (IsBoundIncluded(side)) {
614 return lval == rval + diff;
615 } else {
616 return lval + diff == rval;
617 }
618 }
619 return false;
620}
621
622template <typename T>
623BoundedRange<T>::BoundedRange() noexcept(kNothrowValueCtor)
624 : value_{T{}, T{}, RangeBound::kBoth} {}
625
626template <typename T>
627template <typename U, typename>
628BoundedRange<T>::BoundedRange(U&& lower, U&& upper, RangeBounds bounds)
629 : value_{std::forward<U>(lower), std::forward<U>(upper), bounds} {}
630
631template <typename T>
632template <typename U>
633BoundedRange<T>::BoundedRange(Range<U>&& rhs) : value_{std::move(rhs)} {
634 if (value_.Empty()) {
635 throw BoundedRangeError{"empty range"};
636 }
637 if (!value_.HasLowerBound()) {
638 throw BoundedRangeError{"lower bound is missing"};
639 }
640 if (!value_.HasUpperBound()) {
641 throw BoundedRangeError{"upper bound is missing"};
642 }
643}
644
645template <typename T>
646bool BoundedRange<T>::operator==(const BoundedRange& rhs) const {
647 return value_ == rhs.value_;
648}
649
650template <typename T>
651std::ostream& operator<<(std::ostream& os, const BoundedRange<T>& val) {
652 return os << val.GetUnboundedRange();
653}
654} // namespace storages::postgres
655
656// TODO fmt::format specializations on user demand
657
658USERVER_NAMESPACE_END