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 kIsFixedSizeContainer<T>,
49 detail::HasFixedDimensionsImpl<T>,
50 BoolConstant<!kIsCompatibleContainer<T>>>::type {};
51
52template <typename Container>
53inline constexpr bool kHasFixedDimensions = HasFixedDimensions<Container>::value;
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(kIsFixedSizeContainer<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 kIsFixedSizeContainer<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 (traits::kIsFixedSizeContainer<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::kCanClear<Element>) {
225 elem.clear();
226 }
227 if constexpr (traits::kCanReserve<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>
392constexpr bool EnableArrayParser() {
393 if constexpr (!traits::kIsCompatibleContainer<Container>) {
394 return false;
395 } else {
396 using ElementType = typename traits::ContainerFinalElement<Container>::type;
397 return traits::kHasParser<ElementType>;
398 }
399}
400template <typename Container>
401inline constexpr bool kEnableArrayParser = EnableArrayParser<Container>();
402
403template <typename Container>
404constexpr bool EnableArrayFormatter() {
405 if constexpr (!traits::kIsCompatibleContainer<Container>) {
406 return false;
407 } else {
408 using ElementType = typename traits::ContainerFinalElement<Container>::type;
409 return traits::kHasFormatter<ElementType>;
410 }
411}
412template <typename Container>
413inline constexpr bool kEnableArrayFormatter = EnableArrayFormatter<Container>();
414
415} // namespace detail
416
417template <typename T>
418struct CppToPg<T, std::enable_if_t<traits::detail::EnableContainerMapping<T>()>>
419 : detail::ArrayPgOid<T, detail::IsElementMappedToSystem<T>()> {};
420
421template <typename T>
422constexpr bool IsTypeMappedToSystemArray() {
423 return traits::kIsMappedToPg<T> &&
424 std::is_same<typename CppToPg<T>::Mapping, io::detail::ArrayPgOid<typename CppToPg<T>::Type, true>>::value;
425}
426
427namespace traits {
428
429template <typename T>
430struct Input<T, std::enable_if_t<!detail::kCustomParserDefined<T> && io::detail::kEnableArrayParser<T>>> {
431 using type = io::detail::ArrayBinaryParser<T>;
432};
433
434template <typename T>
435struct Output<T, std::enable_if_t<!detail::kCustomFormatterDefined<T> && io::detail::kEnableArrayFormatter<T>>> {
436 using type = io::detail::ArrayBinaryFormatter<T>;
437};
438
439template <typename T>
440struct ParserBufferCategory<io::detail::ArrayBinaryParser<T>>
441 : std::integral_constant<BufferCategory, BufferCategory::kArrayBuffer> {};
442
443// std::vector
444template <typename... T>
445struct IsCompatibleContainer<std::vector<T...>> : std::true_type {};
446
447// std::array
448template <typename T, std::size_t Size>
449struct IsCompatibleContainer<std::array<T, Size>> : std::true_type {};
450
451// std::set
452template <typename... T>
453struct IsCompatibleContainer<std::set<T...>> : std::true_type {};
454
455// std::unordered_set
456template <typename... T>
457struct IsCompatibleContainer<std::unordered_set<T...>> : std::true_type {};
458
459// TODO Add more containers
460
461} // namespace traits
462
463namespace detail {
464
465/// A helper data type to write a container chunk to postgresql array buffer
466/// Mimics container interface (type aliases + begin/end)
467template <typename Container>
468class ContainerChunk {
469public:
470 static_assert(
471 traits::IsCompatibleContainer<Container>{},
472 "Only containers explicitly declared as compatible are supported"
473 );
474
475 using value_type = typename Container::value_type;
476 using const_iterator_type = typename Container::const_iterator;
477
478 ContainerChunk(const_iterator_type begin, std::size_t size)
479 : begin_{begin},
480 end_{std::next(begin, size)},
481 size_{size}
482 {}
483
484 std::size_t size() const { return size_; }
485 bool empty() const { return begin_ == end_; }
486
487 const_iterator_type begin() const { return begin_; }
488 const_iterator_type cbegin() const { return begin_; }
489
490 const_iterator_type end() const { return end_; }
491 const_iterator_type cend() const { return end_; }
492
493private:
494 const_iterator_type begin_;
495 const_iterator_type end_;
496 std::size_t size_;
497};
498
499/// Utility class to iterate chunks of input array
500template <typename Container>
501class ContainerSplitter {
502public:
503 static_assert(
504 traits::IsCompatibleContainer<Container>{},
505 "Only containers explicitly declared as compatible are supported"
506 );
507
508 using value_type = ContainerChunk<Container>;
509
510 class ChunkIterator {
511 public:
512 using UnderlyingIterator = typename Container::const_iterator;
513 ChunkIterator(const Container& container, UnderlyingIterator current, std::size_t chunk_elements)
514 : container_{container},
515 chunk_size_{chunk_elements},
516 tail_size_{static_cast<size_t>(std::distance(current, container_.end()))},
517 current_{current}
518 {}
519
520 bool operator==(const ChunkIterator& rhs) const { return current_ == rhs.current_; }
521
522 bool operator!=(const ChunkIterator& rhs) const { return !(*this == rhs); }
523
524 value_type operator*() const { return {current_, NextStep()}; }
525
526 ChunkIterator& operator++() {
527 auto step = NextStep();
528 std::advance(current_, step);
529 tail_size_ -= step;
530 return *this;
531 }
532
533 ChunkIterator operator++(int) {
534 ChunkIterator tmp{*this};
535 ++(*this);
536 return tmp;
537 }
538
539 std::size_t TailSize() const { return tail_size_; }
540
541 private:
542 std::size_t NextStep() const { return std::min(chunk_size_, tail_size_); }
543
544 const Container& container_;
545 const std::size_t chunk_size_;
546 std::size_t tail_size_;
547 UnderlyingIterator current_;
548 };
549
550 ContainerSplitter(const Container& container, std::size_t chunk_elements)
551 : container_{container},
552 chunk_size_{chunk_elements}
553 {}
554
555 std::size_t size() const {
556 auto sz = container_.size();
557 return sz / chunk_size_ + (sz % chunk_size_ ? 1 : 0);
558 }
559 bool empty() const { return container_.empty(); }
560
561 ChunkIterator begin() const { return {container_, container_.begin(), chunk_size_}; }
562
563 ChunkIterator end() const { return {container_, container_.end(), chunk_size_}; }
564
565 std::size_t ChunkSize() const { return chunk_size_; }
566 const Container& GetContainer() const { return container_; }
567
568private:
569 const Container& container_;
570 const std::size_t chunk_size_;
571};
572
573template <
574 typename Container,
575 typename Seq = std::make_index_sequence<boost::pfr::tuple_size_v<typename Container::value_type>>>
576struct ColumnsSplitterHelper;
577
578/// Utility helper to iterate chunks of input array in column-wise way
579template <typename Container, std::size_t... Indexes>
580struct ColumnsSplitterHelper<Container, std::index_sequence<Indexes...>> final {
581 static_assert(sizeof...(Indexes) > 0, "The aggregate having 0 fields doesn't make sense");
582
583 template <std::size_t Index>
584 struct FieldProjection {
585 using RowType = typename Container::value_type;
586 using FieldType = boost::pfr::tuple_element_t<Index, typename Container::value_type>;
587
588 const FieldType& operator()(const RowType& value) const noexcept { return boost::pfr::get<Index>(value); }
589 };
590
591 template <std::size_t Index>
592 using FieldView = USERVER_NAMESPACE::utils::impl::ProjectingView<const Container, FieldProjection<Index>>;
593
594 template <typename Fn>
595 static void Perform(const Container& container, std::size_t chunk_elements, const Fn& fn) {
596 DoSplitByChunks(chunk_elements, fn, FieldView<Indexes>{container}...);
597 }
598
599private:
600 template <typename Fn, typename... Views>
601 static void DoSplitByChunks(std::size_t chunk_elements, const Fn& fn, const Views&... views) {
602 DoIterateByChunks(fn, ContainerSplitter{views, chunk_elements}.begin()...);
603 }
604
605 template <typename Fn, typename FirstChunkIterator, typename... ChunkIterators>
606 static void DoIterateByChunks(const Fn& fn, FirstChunkIterator first, ChunkIterators... chunks) {
607 while (first.TailSize() > 0) {
608 fn(*first, *chunks...);
609
610 ++first;
611 (++chunks, ...);
612 }
613 }
614};
615
616template <
617 typename Container,
618 typename Seq = std::make_index_sequence<boost::pfr::tuple_size_v<typename Container::value_type>>>
619struct ColumnsDecomposerHelper;
620
621template <typename Container, std::size_t... Indexes>
622struct ColumnsDecomposerHelper<Container, std::index_sequence<Indexes...>> final {
623 static_assert(sizeof...(Indexes) > 0, "The aggregate having 0 fields doesn't make sense");
624
625 template <std::size_t Index>
626 struct FieldProjection {
627 using RowType = typename Container::value_type;
628 using FieldType = boost::pfr::tuple_element_t<Index, typename Container::value_type>;
629
630 const FieldType& operator()(const RowType& value) const noexcept { return boost::pfr::get<Index>(value); }
631 };
632
633 template <std::size_t Index>
634 using FieldView = USERVER_NAMESPACE::utils::impl::ProjectingView<const Container, FieldProjection<Index>>;
635
636 template <typename Fn>
637 static auto Perform(const Container& container, const Fn& fn) {
638 return fn(FieldView<Indexes>{container}...);
639 }
640};
641
642/// Utility class to iterate chunks of input array in column-wise way
643template <typename Container>
644class ContainerByColumnsSplitter final {
645public:
646 ContainerByColumnsSplitter(const Container& container, std::size_t chunk_elements)
647 : container_{container},
648 chunk_elements_{chunk_elements}
649 {}
650
651 template <typename Fn>
652 void Perform(const Fn& fn) {
653 ColumnsSplitterHelper<Container>::Perform(container_, chunk_elements_, fn);
654 }
655
656private:
657 const Container& container_;
658 const std::size_t chunk_elements_;
659};
660
661template <typename Container>
662class ContainerByColumnsDecomposer final {
663public:
664 ContainerByColumnsDecomposer(const Container& container)
665 : container_(container)
666 {}
667
668 template <typename Fn>
669 auto Perform(const Fn& fn) {
670 return ColumnsDecomposerHelper<Container>::Perform(container_, fn);
671 }
672
673private:
674 const Container& container_;
675};
676
677} // namespace detail
678
679namespace traits {
680template <typename Container>
681struct IsCompatibleContainer<io::detail::ContainerChunk<Container>> : IsCompatibleContainer<Container> {};
682
683template <typename Container, typename Projection>
684struct IsCompatibleContainer<USERVER_NAMESPACE::utils::impl::ProjectingView<const Container, Projection>>
685 : IsCompatibleContainer<Container> {};
686} // namespace traits
687
688template <typename Container>
689detail::ContainerSplitter<Container> SplitContainer(const Container& container, std::size_t chunk_elements) {
690 return {container, chunk_elements};
691}
692
693template <typename Container>
694detail::ContainerByColumnsSplitter<Container> SplitContainerByColumns(
695 const Container& container,
696 std::size_t chunk_elements
697) {
698 return {container, chunk_elements};
699}
700
701template <typename Container>
702detail::ContainerByColumnsDecomposer<Container> DecomposeContainerByColumns(const Container& container) {
703 return {container};
704}
705
706} // namespace storages::postgres::io
707
708USERVER_NAMESPACE_END