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