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