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>
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>
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>
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 iterator_category = std::input_iterator_tag;
501 using difference_type = std::ptrdiff_t;
502 using value_type = ContainerChunk<Container>;
503 using reference = value_type;
504 using pointer = void;
505
506 using UnderlyingIterator = typename Container::const_iterator;
507 ChunkIterator(const Container& container, UnderlyingIterator current, std::size_t chunk_elements)
508 : chunk_size_{chunk_elements},
509 tail_size_{static_cast<size_t>(std::distance(current, container.end()))},
510 current_{current}
511 {}
512
513 bool operator==(const ChunkIterator& rhs) const { return current_ == rhs.current_; }
514
515 value_type operator*() const { return {current_, NextStep()}; }
516
517 ChunkIterator& operator++() {
518 auto step = NextStep();
519 std::advance(current_, step);
520 tail_size_ -= step;
521 return *this;
522 }
523
524 ChunkIterator operator++(int) {
525 ChunkIterator tmp{*this};
526 ++(*this);
527 return tmp;
528 }
529
530 std::size_t TailSize() const { return tail_size_; }
531
532 private:
533 std::size_t NextStep() const { return std::min(chunk_size_, tail_size_); }
534
535 std::size_t chunk_size_;
536 std::size_t tail_size_;
537 UnderlyingIterator current_;
538 };
539
540 ContainerSplitter(const Container& container, std::size_t chunk_elements)
541 : container_{container},
542 chunk_size_{chunk_elements}
543 {}
544
545 std::size_t size() const {
546 auto sz = container_.size();
547 return sz / chunk_size_ + (sz % chunk_size_ ? 1 : 0);
548 }
549 bool empty() const { return container_.empty(); }
550
551 ChunkIterator begin() const { return {container_, container_.begin(), chunk_size_}; }
552
553 ChunkIterator end() const { return {container_, container_.end(), chunk_size_}; }
554
555 std::size_t ChunkSize() const { return chunk_size_; }
556 const Container& GetContainer() const { return container_; }
557
558private:
559 const Container& container_;
560 const std::size_t chunk_size_;
561};
562
563template <
564 typename Container,
565 typename Seq = std::make_index_sequence<boost::pfr::tuple_size_v<typename Container::value_type>>>
566struct ColumnsSplitterHelper;
567
568/// Utility helper to iterate chunks of input array in column-wise way
569template <typename Container, std::size_t... Indexes>
570struct ColumnsSplitterHelper<Container, std::index_sequence<Indexes...>> final {
571 static_assert(sizeof...(Indexes) > 0, "The aggregate having 0 fields doesn't make sense");
572
573 template <std::size_t Index>
574 struct FieldProjection {
575 using RowType = typename Container::value_type;
576 using FieldType = boost::pfr::tuple_element_t<Index, typename Container::value_type>;
577
578 const FieldType& operator()(const RowType& value) const noexcept { return boost::pfr::get<Index>(value); }
579 };
580
581 template <std::size_t Index>
582 using FieldView = USERVER_NAMESPACE::utils::impl::ProjectingView<const Container, FieldProjection<Index>>;
583
584 template <typename Fn>
585 static void Perform(const Container& container, std::size_t chunk_elements, const Fn& fn) {
586 DoSplitByChunks(chunk_elements, fn, FieldView<Indexes>{container}...);
587 }
588
589private:
590 template <typename Fn, typename... Views>
591 static void DoSplitByChunks(std::size_t chunk_elements, const Fn& fn, const Views&... views) {
592 DoIterateByChunks(fn, ContainerSplitter{views, chunk_elements}.begin()...);
593 }
594
595 template <typename Fn, typename FirstChunkIterator, typename... ChunkIterators>
596 static void DoIterateByChunks(const Fn& fn, FirstChunkIterator first, ChunkIterators... chunks) {
597 while (first.TailSize() > 0) {
598 fn(*first, *chunks...);
599
600 ++first;
601 (++chunks, ...);
602 }
603 }
604};
605
606template <
607 typename Container,
608 typename Seq = std::make_index_sequence<boost::pfr::tuple_size_v<typename Container::value_type>>>
609struct ColumnsDecomposerHelper;
610
611template <typename Container, std::size_t... Indexes>
612struct ColumnsDecomposerHelper<Container, std::index_sequence<Indexes...>> final {
613 static_assert(sizeof...(Indexes) > 0, "The aggregate having 0 fields doesn't make sense");
614
615 template <std::size_t Index>
616 struct FieldProjection {
617 using RowType = typename Container::value_type;
618 using FieldType = boost::pfr::tuple_element_t<Index, typename Container::value_type>;
619
620 const FieldType& operator()(const RowType& value) const noexcept { return boost::pfr::get<Index>(value); }
621 };
622
623 template <std::size_t Index>
624 using FieldView = USERVER_NAMESPACE::utils::impl::ProjectingView<const Container, FieldProjection<Index>>;
625
626 template <typename Fn>
627 static auto Perform(const Container& container, const Fn& fn) {
628 return fn(FieldView<Indexes>{container}...);
629 }
630};
631
632/// Utility class to iterate chunks of input array in column-wise way
633template <typename Container>
634class ContainerByColumnsSplitter final {
635public:
636 ContainerByColumnsSplitter(const Container& container, std::size_t chunk_elements)
637 : container_{container},
638 chunk_elements_{chunk_elements}
639 {}
640
641 template <typename Fn>
642 void Perform(const Fn& fn) {
643 ColumnsSplitterHelper<Container>::Perform(container_, chunk_elements_, fn);
644 }
645
646private:
647 const Container& container_;
648 const std::size_t chunk_elements_;
649};
650
651template <typename Container>
652class ContainerByColumnsDecomposer final {
653public:
654 ContainerByColumnsDecomposer(const Container& container)
655 : container_(container)
656 {}
657
658 template <typename Fn>
659 auto Perform(const Fn& fn) {
660 return ColumnsDecomposerHelper<Container>::Perform(container_, fn);
661 }
662
663private:
664 const Container& container_;
665};
666
667} // namespace detail
668
669namespace traits {
670template <typename Container>
671struct IsCompatibleContainer<io::detail::ContainerChunk<Container>> : IsCompatibleContainer<Container> {};
672
673template <typename Container, typename Projection>
674struct IsCompatibleContainer<USERVER_NAMESPACE::utils::impl::ProjectingView<const Container, Projection>>
675 : IsCompatibleContainer<Container> {};
676} // namespace traits
677
678template <typename Container>
679detail::ContainerSplitter<Container> SplitContainer(const Container& container, std::size_t chunk_elements) {
680 return {container, chunk_elements};
681}
682
683template <typename Container>
684detail::ContainerByColumnsSplitter<Container> SplitContainerByColumns(
685 const Container& container,
686 std::size_t chunk_elements
687) {
688 return {container, chunk_elements};
689}
690
691template <typename Container>
692detail::ContainerByColumnsDecomposer<Container> DecomposeContainerByColumns(const Container& container) {
693 return {container};
694}
695
696} // namespace storages::postgres::io
697
698USERVER_NAMESPACE_END