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#include <optional>
8#include <ostream>
9
10#include <fmt/format.h>
11
12#include <userver/storages/postgres/exceptions.hpp>
13#include <userver/storages/postgres/io/buffer_io_base.hpp>
14#include <userver/storages/postgres/io/field_buffer.hpp>
15#include <userver/storages/postgres/io/traits.hpp>
16#include <userver/storages/postgres/io/type_mapping.hpp>
17#include <userver/storages/postgres/io/type_traits.hpp>
18#include <userver/storages/postgres/io/user_types.hpp>
19
20#include <userver/utils/assert.hpp>
21#include <userver/utils/flags.hpp>
22
23USERVER_NAMESPACE_BEGIN
24
25namespace storages::postgres {
26
27struct UnboundedType {};
28constexpr UnboundedType kUnbounded{};
29
30enum class RangeBound {
31 kNone = 0x00,
32 kLower = 0x01,
33 kUpper = 0x02,
34 kBoth = kLower | kUpper,
35};
36
37using RangeBounds = USERVER_NAMESPACE::utils::Flags<RangeBound>;
38
39template <typename T>
40class Range {
41 static constexpr bool kNothrowValueCtor =
42 std::is_nothrow_default_constructible_v<T>;
43 static constexpr bool kNothrowValueCopy =
44 std::is_nothrow_copy_constructible_v<T>;
45 static constexpr bool kNothrowValueMove =
46 std::is_nothrow_move_constructible_v<T>;
47 static constexpr bool kIsDiscreteValue = std::is_integral_v<T>;
48
49 public:
50 using OptionalValue = std::optional<T>;
51
52 /// Empty range
53 Range() = default;
54
55 /// Unbounded range
56 Range(UnboundedType, UnboundedType) noexcept : data{RangeData{}} {}
57
58 /// Bounded range
59 template <typename U, typename = std::enable_if_t<
61 Range(U&& lower, U&& upper, RangeBounds bounds = RangeBound::kLower);
62
63 /// Range with a lower bound
64 template <typename U, typename = std::enable_if_t<
66 Range(U&& lower, UnboundedType ub,
67 RangeBounds bounds = RangeBound::kLower) noexcept(kNothrowValueCopy);
68
69 /// Range with an upper bound
70 template <typename U, typename = std::enable_if_t<
72 Range(UnboundedType ub, U&& upper,
73 RangeBounds bounds = RangeBound::kNone) noexcept(kNothrowValueCopy);
74
75 Range(const OptionalValue& lower, const OptionalValue& upper,
76 RangeBounds bounds);
77
78 /// Convert from a range of different type.
79 ///
80 /// Intentionally implicit
81 template <typename U,
82 typename = std::enable_if_t<std::is_convertible_v<U, T>>>
83 Range(const Range<U>& rhs);
84
85 bool operator==(const Range& rhs) const;
86
87 bool operator!=(const Range& rhs) const { return !(*this == rhs); }
88
89 bool Empty() const { return !data; }
90
91 /// Make the range empty
92 void Clear() { data.reset(); }
93
94 bool HasLowerBound() const {
95 return !!data && data->HasBound(RangeBound::kLower);
96 }
97 bool HasUpperBound() const {
98 return !!data && data->HasBound(RangeBound::kUpper);
99 }
100
101 /// Get the lower bound.
102 const OptionalValue& GetLowerBound() const {
103 if (!!data) {
104 return data->GetOptionalValue(RangeBound::kLower);
105 }
106 return kNoValue;
107 }
108
109 /// Get the upper bound.
110 const OptionalValue& GetUpperBound() const {
111 if (!!data) {
112 return data->GetOptionalValue(RangeBound::kUpper);
113 }
114 return kNoValue;
115 }
116
117 bool IsLowerBoundIncluded() const {
118 return !!data && data->IsBoundIncluded(RangeBound::kLower);
119 }
120 bool IsUpperBoundIncluded() const {
121 return !!data && data->IsBoundIncluded(RangeBound::kUpper);
122 }
123
124 private:
125 template <typename U>
126 friend class Range;
127
128 struct RangeData {
129 // Unbounded range
130 RangeData() noexcept = default;
131
132 template <typename U>
133 RangeData(U&& lower, U&& upper, RangeBounds bounds)
134 : RangeData{OptionalValue{std::forward<U>(lower)},
135 OptionalValue{std::forward<U>(upper)}, bounds} {}
136
137 template <typename U>
138 RangeData(U&& lower, UnboundedType,
139 RangeBounds bounds) noexcept(kNothrowValueCopy)
140 : RangeData{OptionalValue{std::forward<U>(lower)}, OptionalValue{},
141 bounds} {}
142
143 template <typename U>
144 RangeData(UnboundedType, U&& upper,
145 RangeBounds bounds) noexcept(kNothrowValueCopy)
146 : RangeData{OptionalValue{}, OptionalValue{std::forward<U>(upper)},
147 bounds} {}
148
149 RangeData(OptionalValue low, OptionalValue up, RangeBounds bounds)
150 : bounds{bounds}, lower{std::move(low)}, upper{std::move(up)} {
151 if (lower && upper && *upper < *lower) {
152 throw LogicError("Range lower bound is greater than upper");
153 }
154 }
155
156 bool operator==(const RangeData& rhs) const {
157 return BoundEqual(rhs, RangeBound::kLower) &&
158 BoundEqual(rhs, RangeBound::kUpper);
159 }
160
161 bool operator!=(const RangeData& rhs) const { return !(*this == rhs); }
162
163 bool HasBound(RangeBounds side) const;
164
165 bool IsBoundIncluded(RangeBounds side) const {
166 return HasBound(side) && (bounds & side);
167 }
168
169 bool BoundEqual(const RangeData& rhs, RangeBounds side) const;
170
171 // Using this function without checking is ub
172 const T& GetBoundValue(RangeBounds side) const {
173 if (side == RangeBound::kLower) return *lower;
174 UASSERT_MSG(side == RangeBound::kUpper,
175 "Invalid bounds side argument value");
176 return *upper;
177 }
178
179 const OptionalValue& GetOptionalValue(RangeBounds side) const {
180 if (side == RangeBound::kLower) return lower;
181 UASSERT_MSG(side == RangeBound::kUpper,
182 "Invalid bounds side argument value");
183 return upper;
184 }
185
186 RangeBounds bounds = RangeBound::kNone;
187 OptionalValue lower;
188 OptionalValue upper;
189 };
190
191 template <typename U>
192 static OptionalValue ConvertBound(const std::optional<U>& rhs) {
193 if (!rhs) return OptionalValue{};
194 return OptionalValue{*rhs};
195 }
196
197 template <typename U>
198 static std::optional<RangeData> ConvertData(const Range<U>& rhs) {
199 if (!rhs.data) return {};
200 return RangeData{ConvertBound(rhs.data->lower),
201 ConvertBound(rhs.data->upper), rhs.data->bounds};
202 }
203
204 std::optional<RangeData> data;
205
206 static const inline OptionalValue kNoValue{};
207};
208
209template <typename T>
210auto MakeRange(T&& lower, T&& upper, RangeBounds bounds = RangeBound::kLower) {
211 using ElementType = std::decay_t<T>;
212 return Range<ElementType>{std::forward<T>(lower), std::forward<T>(upper),
213 bounds};
214}
215
216template <typename T>
217auto MakeRange(T&& lower, UnboundedType,
218 RangeBounds bounds = RangeBound::kLower) {
219 using ElementType = std::decay_t<T>;
220 return Range<ElementType>{std::forward<T>(lower), kUnbounded, bounds};
221}
222
223template <typename T>
224auto MakeRange(UnboundedType, T&& upper,
225 RangeBounds bounds = RangeBound::kNone) {
226 using ElementType = std::decay_t<T>;
227 return Range<ElementType>{kUnbounded, std::forward<T>(upper), bounds};
228}
229
230using IntegerRange = Range<Integer>;
231using BigintRange = Range<Bigint>;
232
233template <typename T>
235 static constexpr bool kNothrowValueCtor =
236 std::is_nothrow_default_constructible_v<T>;
237
238 public:
239 using ValueType = T;
240
241 BoundedRange() noexcept(kNothrowValueCtor);
242
243 template <typename U, typename = std::enable_if_t<
244 std::is_convertible_v<std::decay_t<U>, T>>>
245 BoundedRange(U&& lower, U&& upper, RangeBounds bounds = RangeBound::kLower);
246
247 template <typename U>
248 explicit BoundedRange(Range<U>&&);
249
250 bool operator==(const BoundedRange& rhs) const;
251 bool operator!=(const BoundedRange& rhs) const { return !(*this == rhs); }
252
253 const ValueType& GetLowerBound() const { return *value_.GetLowerBound(); }
254 bool IsLowerBoundIncluded() const { return value_.IsLowerBoundIncluded(); }
255
256 const ValueType& GetUpperBound() const { return *value_.GetUpperBound(); }
257 bool IsUpperBoundIncluded() const { return value_.IsUpperBoundIncluded(); }
258
259 const Range<T>& GetUnboundedRange() const { return value_; }
260
261 // TODO Intersection and containment test functions on user demand
262 private:
263 Range<T> value_;
264};
265
266using BoundedIntegerRange = BoundedRange<Integer>;
267using BoundedBigintRange = BoundedRange<Bigint>;
268
269} // namespace storages::postgres
270
271namespace storages::postgres::io {
272
273namespace detail {
274
275enum class RangeFlag {
276 kNone = 0x00,
277 kEmpty = 0x01,
278 kLowerBoundInclusive = 0x02,
279 kUpperBoundInclusive = 0x04,
280 kLowerBoundInfinity = 0x08,
281 kUpperBoundInfinity = 0x10,
282 kLowerBoundNull = 0x20,
283 kUpperBoundNull = 0x40,
284 kContainEmpty = 0x80,
285};
286
287using RangeFlags = USERVER_NAMESPACE::utils::Flags<RangeFlag>;
288
289constexpr bool HasLowerBound(RangeFlags flags) {
290 return !(flags & RangeFlags{RangeFlag::kEmpty, RangeFlag::kLowerBoundNull,
291 RangeFlag::kLowerBoundInfinity});
292}
293
294constexpr bool HasUpperBound(RangeFlags flags) {
295 return !(flags & RangeFlags{RangeFlag::kEmpty, RangeFlag::kUpperBoundNull,
296 RangeFlag::kUpperBoundInfinity});
297}
298
299template <typename T>
300struct RangeBinaryParser : BufferParserBase<Range<T>> {
301 using BaseType = BufferParserBase<Range<T>>;
302 using ValueType = typename BaseType::ValueType;
303 using ElementType = T;
304 using ElementParser = typename traits::IO<ElementType>::ParserType;
305
306 static constexpr BufferCategory element_buffer_category =
307 traits::kParserBufferCategory<ElementParser>;
308
309 using BaseType::BaseType;
310
311 void operator()(FieldBuffer buffer, const TypeBufferCategory& categories) {
312 char wire_range_flags{0};
313
314 buffer.Read(wire_range_flags, BufferCategory::kPlainBuffer);
315 RangeFlags range_flags(static_cast<RangeFlag>(wire_range_flags));
316
317 ValueType wire_value;
318 if (range_flags != RangeFlag::kEmpty) {
319 RangeBounds bounds = RangeBound::kNone;
320 typename ValueType::OptionalValue lower;
321 typename ValueType::OptionalValue upper;
322 if (HasLowerBound(range_flags)) {
323 if (range_flags & RangeFlag::kLowerBoundInclusive) {
324 bounds |= RangeBound::kLower;
325 }
326 T tmp;
327 buffer.ReadRaw(tmp, categories, element_buffer_category);
328 lower = tmp;
329 }
330 if (HasUpperBound(range_flags)) {
331 if (range_flags & RangeFlag::kUpperBoundInclusive) {
332 bounds |= RangeBound::kUpper;
333 }
334 T tmp;
335 buffer.ReadRaw(tmp, categories, element_buffer_category);
336 upper = tmp;
337 }
338 wire_value = ValueType{lower, upper, bounds};
339 }
340 this->value = wire_value;
341 }
342};
343
344template <typename T>
345struct RangeBinaryFormatter : BufferFormatterBase<Range<T>> {
346 using BaseType = BufferFormatterBase<Range<T>>;
347
348 using BaseType::BaseType;
349
350 template <typename Buffer>
351 void operator()(const UserTypes& types, Buffer& buffer) const {
352 RangeFlags range_flags;
353 if (this->value.Empty()) {
354 range_flags |= RangeFlag::kEmpty;
355 } else {
356 // Mark lower/upper bound
357 if (!this->value.HasLowerBound()) {
358 range_flags |= RangeFlag::kLowerBoundInfinity;
359 } else if (this->value.IsLowerBoundIncluded()) {
360 range_flags |= RangeFlag::kLowerBoundInclusive;
361 }
362 if (!this->value.HasUpperBound()) {
363 range_flags |= RangeFlag::kUpperBoundInfinity;
364 } else if (this->value.IsUpperBoundIncluded()) {
365 range_flags |= RangeFlag::kUpperBoundInclusive;
366 }
367 }
368 char wire_range_flags = static_cast<char>(range_flags.GetValue());
369 io::WriteBuffer(types, buffer, wire_range_flags);
370 if (!this->value.Empty()) {
371 // Write lower/upper bounds
372 if (this->value.HasLowerBound()) {
373 io::WriteRawBinary(types, buffer, this->value.GetLowerBound());
374 }
375 if (this->value.HasUpperBound()) {
376 io::WriteRawBinary(types, buffer, this->value.GetUpperBound());
377 }
378 }
379 }
380};
381
382template <typename T>
383struct BoundedRangeBinaryParser : BufferParserBase<BoundedRange<T>> {
384 using BaseType = BufferParserBase<BoundedRange<T>>;
385 using ValueType = typename BaseType::ValueType;
386
387 using BaseType::BaseType;
388
389 void operator()(FieldBuffer buffer, const TypeBufferCategory& categories) {
390 Range<T> tmp;
391 io::ReadBuffer(buffer, tmp, categories);
392 this->value = ValueType{std::move(tmp)};
393 }
394};
395
396template <typename T>
397struct BoundedRangeBinaryFormatter : BufferFormatterBase<BoundedRange<T>> {
398 using BaseType = BufferFormatterBase<BoundedRange<T>>;
399
400 using BaseType::BaseType;
401
402 template <typename Buffer>
403 void operator()(const UserTypes& types, Buffer& buffer) const {
404 io::WriteBuffer(types, buffer, this->value.GetUnboundedRange());
405 }
406};
407
408} // namespace detail
409
410namespace traits {
411
412template <typename T>
414 using type = io::detail::RangeBinaryParser<T>;
415};
416
417template <typename T>
418struct ParserBufferCategory<io::detail::RangeBinaryParser<T>>
420
421template <typename T>
424};
425
426template <typename T>
429};
430
431template <typename T>
432struct ParserBufferCategory<io::detail::BoundedRangeBinaryParser<T>>
434
435template <typename T>
438};
439
440} // namespace traits
441
442template <>
443struct CppToSystemPg<IntegerRange> : PredefinedOid<PredefinedOids::kInt4Range> {
444};
445template <>
446struct CppToSystemPg<BoundedIntegerRange>
447 : PredefinedOid<PredefinedOids::kInt4Range> {};
448template <>
449struct CppToSystemPg<BigintRange> : PredefinedOid<PredefinedOids::kInt8Range> {
450};
451template <>
452struct CppToSystemPg<BoundedBigintRange>
453 : PredefinedOid<PredefinedOids::kInt8Range> {};
454
455} // namespace storages::postgres::io
456
457namespace storages::postgres {
458
459template <typename T>
460template <typename U, typename>
461Range<T>::Range(U&& lower, U&& upper, RangeBounds bounds)
463 if (lower == upper && bounds != RangeBound::kBoth) {
464 // this will make an empty range
465 data.reset();
466 }
467}
468
469template <typename T>
470template <typename U, typename>
471Range<T>::Range(U&& lower, UnboundedType ub,
472 RangeBounds bounds) noexcept(kNothrowValueCopy)
474
475template <typename T>
476template <typename U, typename>
477Range<T>::Range(UnboundedType ub, U&& upper,
478 RangeBounds bounds) noexcept(kNothrowValueCopy)
480
481template <typename T>
482Range<T>::Range(const OptionalValue& lower, const OptionalValue& upper,
483 RangeBounds bounds)
484 : data{RangeData{lower, upper, bounds}} {}
485
486template <typename T>
487template <typename U, typename>
488Range<T>::Range(const Range<U>& rhs) : data{ConvertData(rhs)} {}
489
490template <typename T>
491bool Range<T>::operator==(const Range& rhs) const {
492 return (Empty() && rhs.Empty()) || (data == rhs.data);
493}
494
495template <typename T>
496std::ostream& operator<<(std::ostream& os, const Range<T>& val) {
497 if (val.Empty()) return os << "empty";
498 if (val.HasLowerBound() && val.IsLowerBoundIncluded())
499 os << '[';
500 else
501 os << '(';
502 if (val.HasLowerBound())
503 os << *val.GetLowerBound();
504 else
505 os << "-inf";
506 os << ", ";
507 if (val.HasUpperBound())
508 os << *val.GetUpperBound();
509 else
510 os << "inf";
511 if (val.HasUpperBound() && val.IsUpperBoundIncluded())
512 os << ']';
513 else
514 os << ')';
515 return os;
516}
517
518template <typename T>
519bool Range<T>::RangeData::HasBound(RangeBounds side) const {
520 if (side == RangeBound::kLower) {
521 return !!lower;
522 }
523 if (side == RangeBound::kUpper) {
524 return !!upper;
525 }
526 return false;
527}
528
529template <typename T>
530bool Range<T>::RangeData::BoundEqual(const RangeData& rhs,
531 RangeBounds side) const {
532 bool has_bound = HasBound(side);
533 if (has_bound != rhs.HasBound(side)) {
534 return false;
535 }
536 if (!has_bound) { // both are unbounded
537 return true;
538 }
539 const auto& lval = GetBoundValue(side);
540 const auto& rval = rhs.GetBoundValue(side);
541 if ((bounds & side) == (rhs.bounds & side)) {
542 // same include
543 return lval == rval;
544 }
545 if constexpr (kIsDiscreteValue) {
546 T diff = (side == RangeBound::kLower ? 1 : -1);
547 if (IsBoundIncluded(side)) {
548 return lval == rval + diff;
549 } else {
550 return lval + diff == rval;
551 }
552 }
553 return false;
554}
555
556template <typename T>
557BoundedRange<T>::BoundedRange() noexcept(kNothrowValueCtor)
558 : value_{T{}, T{}, RangeBound::kBoth} {}
559
560template <typename T>
561template <typename U, typename>
562BoundedRange<T>::BoundedRange(U&& lower, U&& upper, RangeBounds bounds)
563 : value_{std::forward<U>(lower), std::forward<U>(upper), bounds} {}
564
565template <typename T>
566template <typename U>
567BoundedRange<T>::BoundedRange(Range<U>&& rhs) : value_{std::move(rhs)} {
568 if (value_.Empty()) {
569 throw BoundedRangeError{"empty range"};
570 }
571 if (!value_.HasLowerBound()) {
572 throw BoundedRangeError{"lower bound is missing"};
573 }
574 if (!value_.HasUpperBound()) {
575 throw BoundedRangeError{"upper bound is missing"};
576 }
577}
578
579template <typename T>
580bool BoundedRange<T>::operator==(const BoundedRange& rhs) const {
581 return value_ == rhs.value_;
582}
583
584template <typename T>
585std::ostream& operator<<(std::ostream& os, const BoundedRange<T>& val) {
586 return os << val.GetUnboundedRange();
587}
588} // namespace storages::postgres
589
590// TODO fmt::format specializations on user demand
591
592USERVER_NAMESPACE_END