userver: userver/storages/postgres/io/array_types.hpp Source File
Loading...
Searching...
No Matches
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