userver: userver/storages/postgres/io/array_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
array_types.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/io/array_types.hpp
4/// @brief I/O support for arrays (std::array, std::set, std::unordered_set,
5/// std::vector)
6/// @ingroup userver_postgres_parse_and_format
7
8#include <array>
9#include <iterator>
10#include <set>
11#include <unordered_set>
12#include <vector>
13
14#include <boost/pfr/core.hpp>
15
16#include <userver/utils/impl/projecting_view.hpp>
17
18#include <userver/storages/postgres/exceptions.hpp>
19#include <userver/storages/postgres/io/buffer_io_base.hpp>
20#include <userver/storages/postgres/io/field_buffer.hpp>
21#include <userver/storages/postgres/io/row_types.hpp>
22#include <userver/storages/postgres/io/traits.hpp>
23#include <userver/storages/postgres/io/type_mapping.hpp>
24#include <userver/storages/postgres/io/type_traits.hpp>
25#include <userver/storages/postgres/io/user_types.hpp>
26
27USERVER_NAMESPACE_BEGIN
28
29namespace storages::postgres::io {
30
31namespace traits {
32
33template <typename Container>
34struct HasFixedDimensions;
35
36namespace detail {
37
38template <typename Container>
39struct HasFixedDimensionsImpl {
40 using type =
41 typename HasFixedDimensions<typename Container::value_type>::type;
42};
43
44} // namespace detail
45
46template <typename T>
47struct HasFixedDimensions
48 : std::conditional_t<kIsFixedSizeContainer<T>,
49 detail::HasFixedDimensionsImpl<T>,
50 BoolConstant<!kIsCompatibleContainer<T>>>::type {};
51
52template <typename Container>
53inline constexpr bool kHasFixedDimensions =
54 HasFixedDimensions<Container>::value;
55
56template <typename T>
57struct FixedDimensions;
58
59template <typename T>
61
62template <typename T, std::size_t N>
63struct DimensionSize<std::array<T, N>>
65
66template <typename T>
67inline constexpr std::size_t kDimensionSize = DimensionSize<T>::value;
68
69namespace detail {
70
71template <typename A, typename B>
72struct JoinSequences;
73
74template <typename T, T... U, T... V>
75struct JoinSequences<std::integer_sequence<T, U...>,
76 std::integer_sequence<T, V...>> {
77 using type = std::integer_sequence<T, U..., V...>;
78};
79
80template <typename T>
81struct FixedDimensionsImpl {
82 static_assert(kIsFixedSizeContainer<T>, "Container must have fixed size");
83 using type = typename JoinSequences<
84 std::integer_sequence<std::size_t, kDimensionSize<T>>,
85 typename FixedDimensions<typename T::value_type>::type>::type;
86};
87
88template <typename T>
89struct FixedDimensionsNonContainer {
90 using type = std::integer_sequence<std::size_t>;
91};
92
93} // namespace detail
94
95template <typename T, T... Values>
96constexpr std::array<T, sizeof...(Values)> MakeArray(
97 const std::integer_sequence<T, Values...>&) {
98 return {Values...};
99}
100
101template <typename T>
102struct FixedDimensions
103 : std::conditional_t<kIsFixedSizeContainer<T>,
104 detail::FixedDimensionsImpl<T>,
105 detail::FixedDimensionsNonContainer<T>> {};
106
107} // namespace traits
108
109namespace detail {
110
111template <typename Element>
112inline bool ForceInitElementMapping() {
113 // composite types can be parsed without an explicit mapping
114 if constexpr (io::traits::kIsMappedToPg<Element> ||
115 !io::traits::kIsCompositeType<Element>) {
116 return ForceReference(CppToPg<Element>::init_);
117 } else {
118 return true;
119 }
120}
121
122template <typename Container>
123struct ArrayBinaryParser : BufferParserBase<Container> {
124 using BaseType = BufferParserBase<Container>;
125 using ValueType = typename BaseType::ValueType;
126 using ElementType = typename traits::ContainerFinalElement<Container>::type;
127 constexpr static std::size_t dimensions = traits::kDimensionCount<Container>;
128 using Dimensions = std::array<std::size_t, dimensions>;
129 using DimensionIterator = typename Dimensions::iterator;
130 using DimensionConstIterator = typename Dimensions::const_iterator;
131 using ElementMapping = CppToPg<ElementType>;
132
133 using BaseType::BaseType;
134
135 void operator()(FieldBuffer buffer, const TypeBufferCategory& categories) {
136 using std::swap;
137
138 // read dimension count
139 Integer dim_count{0};
140 buffer.Read(dim_count, BufferCategory::kPlainBuffer);
141 if (dim_count != static_cast<Integer>(dimensions) &&
142 ForceInitElementMapping<ElementType>()) {
143 if (dim_count == 0) {
144 ValueType empty{};
145 swap(this->value, empty);
146 return;
147 }
148 throw DimensionMismatch{};
149 }
150
151 // read flags
152 Integer flags{0};
153 buffer.Read(flags, BufferCategory::kPlainBuffer);
154 // TODO check flags
155
156 // read element oid
157 Integer elem_oid{0};
158 buffer.Read(elem_oid, BufferCategory::kPlainBuffer);
159 // TODO check elem_oid
160 auto elem_category = GetTypeBufferCategory(categories, elem_oid);
161
162 // read dimension data
163 Dimensions on_the_wire;
164 for (auto& dim : on_the_wire) {
165 Integer dim_val = 0;
166 buffer.Read(dim_val, BufferCategory::kPlainBuffer);
167 dim = dim_val;
168
169 Integer lbound = 0;
170 buffer.Read(lbound, BufferCategory::kPlainBuffer);
171 }
172 if (!CheckDimensions(on_the_wire)) {
173 throw DimensionMismatch{};
174 }
175 // read elements
176 ValueType tmp;
177 ReadDimension(buffer, on_the_wire.begin(), elem_category, categories, tmp);
178 swap(this->value, tmp);
179 }
180
181 private:
182 bool CheckDimensions(const Dimensions& dims) const {
183 if constexpr (traits::kHasFixedDimensions<Container>) {
184 return dims == traits::MakeArray(
185 typename traits::FixedDimensions<Container>::type{});
186 }
187 return CheckDimensions<ValueType>(dims.begin());
188 }
189 template <typename Element>
190 bool CheckDimensions([[maybe_unused]] DimensionConstIterator dim) const {
191 if constexpr (traits::kIsFixedSizeContainer<Element>) {
192 // check subdimensions
193 if (*dim != traits::kDimensionSize<Element>) {
194 return false;
195 }
196 if constexpr (traits::kDimensionCount<Element> == 1) {
197 return true;
198 } else {
199 return CheckDimensions<typename Element::value_type>(dim + 1);
200 }
201 } else if constexpr (traits::kIsCompatibleContainer<Element>) {
202 if constexpr (traits::kDimensionCount<Element> == 1) {
203 return true;
204 } else {
205 return CheckDimensions<typename Element::value_type>(dim + 1);
206 }
207 }
208 return true;
209 }
210
211 template <typename T>
212 auto GetInserter(T& value) {
213 return std::inserter(value, value.end());
214 }
215
216 template <typename T, std::size_t n>
217 auto GetInserter(std::array<T, n>& array) {
218 return array.begin();
219 }
220
221 template <typename Element>
222 void ReadDimension(FieldBuffer& buffer, DimensionConstIterator dim,
223 BufferCategory elem_category,
224 const TypeBufferCategory& categories, Element& elem) {
225 if constexpr (traits::kIsCompatibleContainer<Element>) {
226 if constexpr (traits::kCanClear<Element>) {
227 elem.clear();
228 }
229 if constexpr (traits::kCanReserve<Element>) {
230 elem.reserve(*dim);
231 }
232 auto it = GetInserter(elem);
233 for (std::size_t i = 0; i < *dim; ++i) {
234 typename Element::value_type val;
235 if constexpr (1 < traits::kDimensionCount<Element>) {
236 // read subdimensions
237 ReadDimension(buffer, dim + 1, elem_category, categories, val);
238 } else {
239 buffer.ReadRaw(val, categories, elem_category);
240 }
241 *it++ = std::move(val);
242 }
243 }
244 }
245
246 void ReadDimension(FieldBuffer& buffer, DimensionConstIterator dim,
247 BufferCategory elem_category,
248 const TypeBufferCategory& categories,
249 std::vector<bool>& elem) {
250 elem.resize(*dim);
251 // NOLINTNEXTLINE(readability-qualified-auto)
252 auto value = elem.begin();
253 for (std::size_t i = 0; i < *dim; ++i) {
254 bool val{false};
255 buffer.ReadRaw(val, categories, elem_category);
256 *value++ = val;
257 }
258 }
259};
260
261template <typename Container>
262struct ArrayBinaryFormatter : BufferFormatterBase<Container> {
263 using BaseType = BufferFormatterBase<Container>;
264 using ValueType = typename BaseType::ValueType;
265 using ArrayMapping = CppToPg<Container>;
266 using ElementType = typename traits::ContainerFinalElement<Container>::type;
267 using ElementMapping = CppToPg<ElementType>;
268 constexpr static std::size_t dimensions = traits::kDimensionCount<Container>;
269 using Dimensions = std::array<std::size_t, dimensions>;
270 using DimensionIterator = typename Dimensions::iterator;
271 using DimensionConstIterator = typename Dimensions::const_iterator;
272
273 using BaseType::BaseType;
274
275 // top level container
276 template <typename Buffer>
277 void operator()(const UserTypes& types, Buffer& buffer,
278 Oid replace_oid = kInvalidOid) const {
279 auto elem_type_oid = ElementMapping::GetOid(types);
280 if (replace_oid != kInvalidOid &&
281 replace_oid != ArrayMapping::GetOid(types)) {
282 elem_type_oid = types.FindElementOid(replace_oid);
283 }
284
285 // Fast path for default-constructed vectors
286 if (this->value.empty()) {
287 io::WriteBuffer(types, buffer, static_cast<Integer>(0)); // dims
288 io::WriteBuffer(types, buffer, static_cast<Integer>(0)); // flags
289 io::WriteBuffer(types, buffer, static_cast<Integer>(elem_type_oid));
290 return;
291 }
292
293 // Write number of dimensions
294 io::WriteBuffer(types, buffer, static_cast<Integer>(dimensions));
295 // Write flags
296 io::WriteBuffer(types, buffer, static_cast<Integer>(0));
297 // Write element type oid
298 io::WriteBuffer(types, buffer, static_cast<Integer>(elem_type_oid));
299 Dimensions dims = GetDimensions();
300 // Write data per dimension
301 WriteDimensionData(types, buffer, dims);
302 // Write flat elements
303 WriteData(types, dims.begin(), buffer, this->value);
304 }
305
306 private:
307 template <typename Element>
308 void CalculateDimensions([[maybe_unused]] DimensionIterator dim,
309 const Element& element) const {
310 if constexpr (traits::kIsCompatibleContainer<Element>) {
311 *dim = element.size();
312 if (!element.empty()) {
313 CalculateDimensions(dim + 1, *element.begin());
314 } // TODO else logic error?
315 }
316 }
317 Dimensions GetDimensions() const {
318 if constexpr (traits::kHasFixedDimensions<Container>) {
319 return traits::MakeArray(
320 typename traits::FixedDimensions<Container>::type{});
321 } else {
322 Dimensions dims{};
323 CalculateDimensions(dims.begin(), this->value);
324 return dims;
325 }
326 }
327 template <typename Buffer>
328 void WriteDimensionData(const UserTypes& types, Buffer& buffer,
329 const Dimensions& dims) const {
330 for (auto dim : dims) {
331 io::WriteBuffer(types, buffer, static_cast<Integer>(dim));
332 io::WriteBuffer(types, buffer, static_cast<Integer>(1)); // lbound
333 }
334 }
335
336 template <typename Buffer, typename Element>
337 void WriteData(const UserTypes& types, DimensionConstIterator dim,
338 Buffer& buffer, const Element& element) const {
339 if (*dim != element.size()) {
340 throw InvalidDimensions{*dim, element.size()};
341 }
342 if constexpr (1 < traits::kDimensionCount<Element>) {
343 // this is a (sub)dimension of array
344 for (const auto& sub : element) {
345 WriteData(types, dim + 1, buffer, sub);
346 }
347 } else {
348 // this is the final dimension
349 for (const auto& sub : element) {
350 io::WriteRawBinary(types, buffer, sub);
351 }
352 }
353 }
354
355 template <typename Buffer>
356 void WriteData(const UserTypes& types, DimensionConstIterator dim,
357 Buffer& buffer, const std::vector<bool>& element) const {
358 if (*dim != element.size()) {
359 throw InvalidDimensions{*dim, element.size()};
360 }
361 for (bool sub : element) {
362 io::WriteRawBinary(types, buffer, sub);
363 }
364 }
365};
366
367template <typename Container, bool System>
368struct ArrayPgOid {
369 using Type = Container;
370 using ElementType = typename traits::ContainerFinalElement<Container>::type;
371 using ElementMapping = CppToPg<ElementType>;
372 using Mapping = ArrayPgOid<Container, System>;
373
374 static Oid GetOid(const UserTypes& types) {
375 return ElementMapping::GetArrayOid(types);
376 }
377};
378
379template <typename Container>
380struct ArrayPgOid<Container, true> {
381 using Type = Container;
382 using ElementType = typename traits::ContainerFinalElement<Container>::type;
383 using ElementMapping = CppToPg<ElementType>;
384 using Mapping = ArrayPgOid<Container, true>;
385
386 static constexpr Oid GetOid(const UserTypes&) {
387 return static_cast<Oid>(ElementMapping::array_oid);
388 }
389};
390
391template <typename Container>
392constexpr bool IsElementMappedToSystem() {
393 if constexpr (!traits::kIsCompatibleContainer<Container>) {
394 return false;
395 } else {
396 return IsTypeMappedToSystem<
397 typename traits::ContainerFinalElement<Container>::type>();
398 }
399}
400
401template <typename Container>
402constexpr bool EnableArrayParser() {
403 if constexpr (!traits::kIsCompatibleContainer<Container>) {
404 return false;
405 } else {
406 using ElementType = typename traits::ContainerFinalElement<Container>::type;
407 return traits::kHasParser<ElementType>;
408 }
409}
410template <typename Container>
411inline constexpr bool kEnableArrayParser = EnableArrayParser<Container>();
412
413template <typename Container>
414constexpr bool EnableArrayFormatter() {
415 if constexpr (!traits::kIsCompatibleContainer<Container>) {
416 return false;
417 } else {
418 using ElementType = typename traits::ContainerFinalElement<Container>::type;
419 return traits::kHasFormatter<ElementType>;
420 }
421}
422template <typename Container>
423inline constexpr bool kEnableArrayFormatter = EnableArrayFormatter<Container>();
424
425} // namespace detail
426
427template <typename T>
430
431template <typename T>
432constexpr bool IsTypeMappedToSystemArray() {
433 return traits::kIsMappedToPg<T> &&
434 std::is_same<
435 typename CppToPg<T>::Mapping,
436 io::detail::ArrayPgOid<typename CppToPg<T>::Type, true>>::value;
437}
438
439namespace traits {
440
441template <typename T>
444 using type = io::detail::ArrayBinaryParser<T>;
445};
446
447template <typename T>
451};
452
453template <typename T>
454struct ParserBufferCategory<io::detail::ArrayBinaryParser<T>>
456
457// std::vector
458template <typename... T>
459struct IsCompatibleContainer<std::vector<T...>> : std::true_type {};
460
461// std::array
462template <typename T, std::size_t Size>
463struct IsCompatibleContainer<std::array<T, Size>> : std::true_type {};
464
465// std::set
466template <typename... T>
467struct IsCompatibleContainer<std::set<T...>> : std::true_type {};
468
469// std::unordered_set
470template <typename... T>
471struct IsCompatibleContainer<std::unordered_set<T...>> : std::true_type {};
472
473// TODO Add more containers
474
475} // namespace traits
476
477namespace detail {
478
479/// A helper data type to write a container chunk to postgresql array buffer
480/// Mimics container interface (type aliases + begin/end)
481template <typename Container>
482class ContainerChunk {
483 public:
484 static_assert(
485 traits::IsCompatibleContainer<Container>{},
486 "Only containers explicitly declared as compatible are supported");
487
488 using value_type = typename Container::value_type;
489 using const_iterator_type = typename Container::const_iterator;
490
491 ContainerChunk(const_iterator_type begin, std::size_t size)
492 : begin_{begin}, end_{std::next(begin, size)}, size_{size} {}
493
494 std::size_t size() const { return size_; }
495 bool empty() const { return begin_ == end_; }
496
497 const_iterator_type begin() const { return begin_; }
498 const_iterator_type cbegin() const { return begin_; }
499
500 const_iterator_type end() const { return end_; }
501 const_iterator_type cend() const { return end_; }
502
503 private:
504 const_iterator_type begin_;
505 const_iterator_type end_;
506 std::size_t size_;
507};
508
509/// Utility class to iterate chunks of input array
510template <typename Container>
511class ContainerSplitter {
512 public:
513 static_assert(
514 traits::IsCompatibleContainer<Container>{},
515 "Only containers explicitly declared as compatible are supported");
516
517 using value_type = ContainerChunk<Container>;
518
519 class ChunkIterator {
520 public:
521 using UnderlyingIterator = typename Container::const_iterator;
522 ChunkIterator(const Container& container, UnderlyingIterator current,
523 std::size_t chunk_elements)
524 : container_{container},
525 chunk_size_{chunk_elements},
526 tail_size_{
527 static_cast<size_t>(std::distance(current, container_.end()))},
528 current_{current} {}
529
530 bool operator==(const ChunkIterator& rhs) const {
531 return current_ == rhs.current_;
532 }
533
534 bool operator!=(const ChunkIterator& rhs) const { return !(*this == rhs); }
535
536 value_type operator*() const { return {current_, NextStep()}; }
537
538 ChunkIterator& operator++() {
539 auto step = NextStep();
540 std::advance(current_, step);
541 tail_size_ -= step;
542 return *this;
543 }
544
545 ChunkIterator operator++(int) {
546 ChunkIterator tmp{*this};
547 ++(*this);
548 return tmp;
549 }
550
551 std::size_t TailSize() const { return tail_size_; }
552
553 private:
554 std::size_t NextStep() const { return std::min(chunk_size_, tail_size_); }
555
556 const Container& container_;
557 const std::size_t chunk_size_;
558 std::size_t tail_size_;
559 UnderlyingIterator current_;
560 };
561
562 ContainerSplitter(const Container& container, std::size_t chunk_elements)
563 : container_{container}, chunk_size_{chunk_elements} {}
564
565 std::size_t size() const {
566 auto sz = container_.size();
567 return sz / chunk_size_ + (sz % chunk_size_ ? 1 : 0);
568 }
569 bool empty() const { return container_.empty(); }
570
571 ChunkIterator begin() const {
572 return {container_, container_.begin(), chunk_size_};
573 }
574
575 ChunkIterator end() const {
576 return {container_, container_.end(), chunk_size_};
577 }
578
579 std::size_t ChunkSize() const { return chunk_size_; }
580 const Container& GetContainer() const { return container_; }
581
582 private:
583 const Container& container_;
584 const std::size_t chunk_size_;
585};
586
587template <typename Container,
588 typename Seq = std::make_index_sequence<
589 boost::pfr::tuple_size_v<typename Container::value_type>>>
590struct ColumnsSplitterHelper;
591
592/// Utility helper to iterate chunks of input array in column-wise way
593template <typename Container, std::size_t... Indexes>
594struct ColumnsSplitterHelper<Container, std::index_sequence<Indexes...>> final {
595 static_assert(sizeof...(Indexes) > 0,
596 "The aggregate having 0 fields doesn't make sense");
597
598 template <std::size_t Index>
599 struct FieldProjection {
600 using RowType = typename Container::value_type;
601 using FieldType =
602 boost::pfr::tuple_element_t<Index, typename Container::value_type>;
603
604 const FieldType& operator()(const RowType& value) const noexcept {
605 return boost::pfr::get<Index>(value);
606 }
607 };
608
609 template <std::size_t Index>
610 using FieldView =
611 USERVER_NAMESPACE::utils::impl::ProjectingView<const Container,
612 FieldProjection<Index>>;
613
614 template <typename Fn>
615 static void Perform(const Container& container, std::size_t chunk_elements,
616 const Fn& fn) {
617 DoSplitByChunks(chunk_elements, fn, FieldView<Indexes>{container}...);
618 }
619
620 private:
621 template <typename Fn, typename... Views>
622 static void DoSplitByChunks(std::size_t chunk_elements, const Fn& fn,
623 const Views&... views) {
624 DoIterateByChunks(fn, ContainerSplitter{views, chunk_elements}.begin()...);
625 }
626
627 template <typename Fn, typename FirstChunkIterator,
628 typename... ChunkIterators>
629 static void DoIterateByChunks(const Fn& fn, FirstChunkIterator first,
630 ChunkIterators... chunks) {
631 while (first.TailSize() > 0) {
632 fn(*first, *chunks...);
633
634 ++first;
635 (++chunks, ...);
636 }
637 }
638};
639
640/// Utility class to iterate chunks of input array in column-wise way
641template <typename Container>
642class ContainerByColumnsSplitter final {
643 public:
644 ContainerByColumnsSplitter(const Container& container,
645 std::size_t chunk_elements)
646 : container_{container}, chunk_elements_{chunk_elements} {}
647
648 template <typename Fn>
649 void Perform(const Fn& fn) {
650 ColumnsSplitterHelper<Container>::Perform(container_, chunk_elements_, fn);
651 }
652
653 private:
654 const Container& container_;
655 const std::size_t chunk_elements_;
656};
657
658} // namespace detail
659
660namespace traits {
661template <typename Container>
662struct IsCompatibleContainer<io::detail::ContainerChunk<Container>>
663 : IsCompatibleContainer<Container> {};
664
665template <typename Container, typename Projection>
667 USERVER_NAMESPACE::utils::impl::ProjectingView<const Container, Projection>>
668 : IsCompatibleContainer<Container> {};
669} // namespace traits
670
671template <typename Container>
672detail::ContainerSplitter<Container> SplitContainer(
673 const Container& container, std::size_t chunk_elements) {
674 return {container, chunk_elements};
675}
676
677template <typename Container>
678detail::ContainerByColumnsSplitter<Container> SplitContainerByColumns(
679 const Container& container, std::size_t chunk_elements) {
680 return {container, chunk_elements};
681}
682
683} // namespace storages::postgres::io
684
685USERVER_NAMESPACE_END