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#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