userver: userver/storages/postgres/io/range_types.hpp Source File
Loading...
Searching...
No Matches
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