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