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, typename = std::enable_if_t<std::is_convertible_v<std::decay_t<U>, T>>>
57 Range(U&& lower, U&& upper, RangeBounds bounds = RangeBound::kLower);
58
59 /// Range with a lower bound
60 template <typename U, typename = std::enable_if_t<std::is_convertible_v<std::decay_t<U>, T>>>
61 Range(U&& lower, UnboundedType ub, RangeBounds bounds = RangeBound::kLower) noexcept(kNothrowValueCopy);
62
63 /// Range with an upper bound
64 template <typename U, typename = std::enable_if_t<std::is_convertible_v<std::decay_t<U>, T>>>
65 Range(UnboundedType ub, U&& upper, RangeBounds bounds = RangeBound::kNone) noexcept(kNothrowValueCopy);
66
67 Range(const OptionalValue& lower, const OptionalValue& upper, RangeBounds bounds);
68
69 /// Convert from a range of different type.
70 ///
71 /// Intentionally implicit
72 template <typename U, typename = std::enable_if_t<std::is_convertible_v<U, T>>>
73 Range(const Range<U>& rhs);
74
75 bool operator==(const Range& rhs) const;
76
77 bool operator!=(const Range& rhs) const { return !(*this == rhs); }
78
79 bool Empty() const { return !data_; }
80
81 /// Make the range empty
82 void Clear() { data_.reset(); }
83
84 bool HasLowerBound() const { return !!data_ && data_->HasBound(RangeBound::kLower); }
85 bool HasUpperBound() const { return !!data_ && data_->HasBound(RangeBound::kUpper); }
86
87 /// Get the lower bound.
88 const OptionalValue& GetLowerBound() const {
89 if (!!data_) {
90 return data_->GetOptionalValue(RangeBound::kLower);
91 }
92 return kNoValue;
93 }
94
95 /// Get the upper bound.
96 const OptionalValue& GetUpperBound() const {
97 if (!!data_) {
98 return data_->GetOptionalValue(RangeBound::kUpper);
99 }
100 return kNoValue;
101 }
102
103 bool IsLowerBoundIncluded() const { return !!data_ && data_->IsBoundIncluded(RangeBound::kLower); }
104 bool IsUpperBoundIncluded() const { return !!data_ && data_->IsBoundIncluded(RangeBound::kUpper); }
105
106private:
107 template <typename U>
108 friend class Range;
109
110 struct RangeData {
111 // Unbounded range
112 RangeData() noexcept = default;
113
114 template <typename U>
115 RangeData(U&& lower, U&& upper, RangeBounds bounds)
116 : RangeData{OptionalValue{std::forward<U>(lower)}, OptionalValue{std::forward<U>(upper)}, bounds}
117 {}
118
119 template <typename U>
120 RangeData(U&& lower, UnboundedType, RangeBounds bounds) noexcept(kNothrowValueCopy)
121 : RangeData{OptionalValue{std::forward<U>(lower)}, OptionalValue{}, bounds}
122 {}
123
124 template <typename U>
125 RangeData(UnboundedType, U&& upper, RangeBounds bounds) noexcept(kNothrowValueCopy)
126 : RangeData{OptionalValue{}, OptionalValue{std::forward<U>(upper)}, bounds}
127 {}
128
129 RangeData(OptionalValue low, OptionalValue up, RangeBounds bounds)
130 : bounds{bounds},
131 lower{std::move(low)},
132 upper{std::move(up)}
133 {
134 if (lower && upper && *upper < *lower) {
135 throw LogicError("Range lower bound is greater than upper");
136 }
137 }
138
139 bool operator==(const RangeData& rhs) const {
140 return BoundEqual(rhs, RangeBound::kLower) && BoundEqual(rhs, RangeBound::kUpper);
141 }
142
143 bool operator!=(const RangeData& rhs) const { return !(*this == rhs); }
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, typename = std::enable_if_t<std::is_convertible_v<std::decay_t<U>, T>>>
225 BoundedRange(U&& lower, U&& upper, RangeBounds bounds = RangeBound::kLower);
226
227 template <typename U>
228 explicit BoundedRange(Range<U>&&);
229
230 bool operator==(const BoundedRange& rhs) const;
231 bool operator!=(const BoundedRange& rhs) const { return !(*this == rhs); }
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 <typename T>
390struct Input<Range<T>, std::enable_if_t<kHasParser<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 <typename T>
399struct Output<Range<T>, std::enable_if_t<kHasFormatter<T>>> {
400 using type = io::detail::RangeBinaryFormatter<T>;
401};
402
403template <typename T>
404struct Input<BoundedRange<T>, std::enable_if_t<kHasParser<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 <typename T>
413struct Output<BoundedRange<T>, std::enable_if_t<kHasFormatter<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, typename>
434Range<T>::Range(U&& lower, U&& upper, RangeBounds bounds)
435 : data_{RangeData{std::forward<U>(lower), std::forward<U>(upper), bounds}}
436{
437 if (lower == upper && bounds != RangeBound::kBoth) {
438 // this will make an empty range
439 data_.reset();
440 }
441}
442
443template <typename T>
444template <typename U, typename>
445Range<T>::Range(U&& lower, UnboundedType ub, RangeBounds bounds) noexcept(kNothrowValueCopy)
446 : data_{RangeData{std::forward<U>(lower), ub, bounds}}
447{}
448
449template <typename T>
450template <typename U, typename>
451Range<T>::Range(UnboundedType ub, U&& upper, RangeBounds bounds) noexcept(kNothrowValueCopy)
452 : data_{RangeData{ub, std::forward<U>(upper), bounds}}
453{}
454
455template <typename T>
456Range<T>::Range(const OptionalValue& lower, const OptionalValue& upper, RangeBounds bounds)
457 : data_{RangeData{lower, upper, bounds}}
458{}
459
460template <typename T>
461template <typename U, typename>
462Range<T>::Range(const Range<U>& rhs)
463 : data_{ConvertData(rhs)}
464{}
465
466template <typename T>
467bool Range<T>::operator==(const Range& rhs) const {
468 return (Empty() && rhs.Empty()) || (data_ == rhs.data_);
469}
470
471template <typename T>
472std::ostream& operator<<(std::ostream& os, const Range<T>& val) {
473 if (val.Empty()) {
474 return os << "empty";
475 }
476 if (val.HasLowerBound() && val.IsLowerBoundIncluded()) {
477 os << '[';
478 } else {
479 os << '(';
480 }
481 if (val.HasLowerBound()) {
482 os << *val.GetLowerBound();
483 } else {
484 os << "-inf";
485 }
486 os << ", ";
487 if (val.HasUpperBound()) {
488 os << *val.GetUpperBound();
489 } else {
490 os << "inf";
491 }
492 if (val.HasUpperBound() && val.IsUpperBoundIncluded()) {
493 os << ']';
494 } else {
495 os << ')';
496 }
497 return os;
498}
499
500template <typename T>
501bool Range<T>::RangeData::HasBound(RangeBounds side) const {
502 if (side == RangeBound::kLower) {
503 return !!lower;
504 }
505 if (side == RangeBound::kUpper) {
506 return !!upper;
507 }
508 return false;
509}
510
511template <typename T>
512bool Range<T>::RangeData::BoundEqual(const RangeData& rhs, RangeBounds side) const {
513 const bool has_bound = HasBound(side);
514 if (has_bound != rhs.HasBound(side)) {
515 return false;
516 }
517 if (!has_bound) { // both are unbounded
518 return true;
519 }
520 const auto& lval = GetBoundValue(side);
521 const auto& rval = rhs.GetBoundValue(side);
522 if ((bounds & side) == (rhs.bounds & side)) {
523 // same include
524 return lval == rval;
525 }
526 if constexpr (kIsDiscreteValue) {
527 T diff = (side == RangeBound::kLower ? 1 : -1);
528 if (IsBoundIncluded(side)) {
529 return lval == rval + diff;
530 } else {
531 return lval + diff == rval;
532 }
533 }
534 return false;
535}
536
537template <typename T>
538BoundedRange<T>::BoundedRange() noexcept(kNothrowValueCtor)
539 : value_{T{}, T{}, RangeBound::kBoth}
540{}
541
542template <typename T>
543template <typename U, typename>
544BoundedRange<T>::BoundedRange(U&& lower, U&& upper, RangeBounds bounds)
545 : value_{std::forward<U>(lower), std::forward<U>(upper), bounds}
546{}
547
548template <typename T>
549template <typename U>
550BoundedRange<T>::BoundedRange(Range<U>&& rhs)
551 : value_{std::move(rhs)}
552{
553 if (value_.Empty()) {
554 throw BoundedRangeError{"empty range"};
555 }
556 if (!value_.HasLowerBound()) {
557 throw BoundedRangeError{"lower bound is missing"};
558 }
559 if (!value_.HasUpperBound()) {
560 throw BoundedRangeError{"upper bound is missing"};
561 }
562}
563
564template <typename T>
565bool BoundedRange<T>::operator==(const BoundedRange& rhs) const {
566 return value_ == rhs.value_;
567}
568
569template <typename T>
570std::ostream& operator<<(std::ostream& os, const BoundedRange<T>& val) {
571 return os << val.GetUnboundedRange();
572}
573} // namespace storages::postgres
574
575// TODO fmt::format specializations on user demand
576
577USERVER_NAMESPACE_END