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