11#include <unordered_set>
14#include <boost/pfr/core.hpp>
16#include <userver/utils/impl/projecting_view.hpp>
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>
27USERVER_NAMESPACE_BEGIN
33template <
typename Container>
34struct HasFixedDimensions;
38template <
typename Container>
39struct HasFixedDimensionsImpl {
41 typename HasFixedDimensions<
typename Container::value_type>::type;
47struct HasFixedDimensions
48 : std::conditional_t<kIsFixedSizeContainer<T>,
49 detail::HasFixedDimensionsImpl<T>,
50 BoolConstant<!kIsCompatibleContainer<T>>>::type {};
52template <
typename Container>
53inline constexpr bool kHasFixedDimensions =
54 HasFixedDimensions<Container>::value;
57struct FixedDimensions;
62template <
typename T, std::size_t N>
67inline constexpr std::size_t kDimensionSize = DimensionSize<T>::value;
71template <
typename A,
typename B>
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...>;
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;
89struct FixedDimensionsNonContainer {
90 using type = std::integer_sequence<std::size_t>;
95template <
typename T, T... Values>
96constexpr std::array<T,
sizeof...(Values)> MakeArray(
97 const std::integer_sequence<T, Values...>&) {
102struct FixedDimensions
103 : std::conditional_t<kIsFixedSizeContainer<T>,
104 detail::FixedDimensionsImpl<T>,
105 detail::FixedDimensionsNonContainer<T>> {};
111template <
typename Element>
112inline bool ForceInitElementMapping() {
114 if constexpr (
io::
traits::kIsMappedToPg<Element> ||
115 !
io::
traits::kIsCompositeType<Element>) {
116 return ForceReference(CppToPg<Element>::init_);
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>;
133 using BaseType::BaseType;
135 void operator()(
FieldBuffer buffer,
const TypeBufferCategory& categories) {
139 Integer dim_count{0};
141 if (dim_count !=
static_cast<Integer>(dimensions) &&
142 ForceInitElementMapping<ElementType>()) {
143 if (dim_count == 0) {
145 swap(
this->value, empty);
160 auto elem_category = GetTypeBufferCategory(categories, elem_oid);
163 Dimensions on_the_wire;
164 for (
auto& dim : on_the_wire) {
172 if (!CheckDimensions(on_the_wire)) {
177 ReadDimension(buffer, on_the_wire.begin(), elem_category, categories, tmp);
178 swap(
this->value, tmp);
182 bool CheckDimensions(
const Dimensions& dims)
const {
183 if constexpr (
traits::kHasFixedDimensions<Container>) {
184 return dims ==
traits::MakeArray(
185 typename traits::FixedDimensions<Container>::type{});
187 return CheckDimensions<ValueType>(dims.begin());
189 template <
typename Element>
190 bool CheckDimensions([[maybe_unused]] DimensionConstIterator dim)
const {
191 if constexpr (
traits::kIsFixedSizeContainer<Element>) {
193 if (*dim !=
traits::kDimensionSize<Element>) {
196 if constexpr (
traits::kDimensionCount<Element> == 1) {
199 return CheckDimensions<
typename Element::value_type>(dim + 1);
201 }
else if constexpr (
traits::kIsCompatibleContainer<Element>) {
202 if constexpr (
traits::kDimensionCount<Element> == 1) {
205 return CheckDimensions<
typename Element::value_type>(dim + 1);
211 template <
typename T>
212 auto GetInserter(T& value) {
213 return std::inserter(value, value.end());
216 template <
typename T, std::size_t n>
217 auto GetInserter(std::array<T, n>& array) {
218 return array.begin();
221 template <
typename Element>
222 void ReadDimension(
FieldBuffer& buffer, DimensionConstIterator dim,
224 const TypeBufferCategory& categories, Element& elem) {
225 if constexpr (
traits::kIsCompatibleContainer<Element>) {
226 if constexpr (
traits::kCanClear<Element>) {
229 if constexpr (
traits::kCanReserve<Element>) {
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>) {
237 ReadDimension(buffer, dim + 1, elem_category, categories, val);
239 buffer.ReadRaw(val, categories, elem_category);
241 *it++ = std::move(val);
246 void ReadDimension(
FieldBuffer& buffer, DimensionConstIterator dim,
248 const TypeBufferCategory& categories,
249 std::vector<
bool>& elem) {
252 auto value = elem.begin();
253 for (std::size_t i = 0; i < *dim; ++i) {
255 buffer.ReadRaw(val, categories, elem_category);
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;
273 using BaseType::BaseType;
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);
286 if (
this->value.empty()) {
287 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
288 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
289 io::WriteBuffer(types, buffer,
static_cast<Integer>(elem_type_oid));
294 io::WriteBuffer(types, buffer,
static_cast<Integer>(dimensions));
296 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
298 io::WriteBuffer(types, buffer,
static_cast<Integer>(elem_type_oid));
299 Dimensions dims = GetDimensions();
301 WriteDimensionData(types, buffer, dims);
303 WriteData(types, dims.begin(), buffer,
this->value);
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());
317 Dimensions GetDimensions()
const {
318 if constexpr (
traits::kHasFixedDimensions<Container>) {
320 typename traits::FixedDimensions<Container>::type{});
323 CalculateDimensions(dims.begin(),
this->value);
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));
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()) {
342 if constexpr (1 <
traits::kDimensionCount<Element>) {
344 for (
const auto& sub : element) {
345 WriteData(types, dim + 1, buffer, sub);
349 for (
const auto& sub : element) {
350 io::WriteRawBinary(types, buffer, sub);
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()};
361 for (
bool sub : element) {
362 io::WriteRawBinary(types, buffer, sub);
367template <
typename Container,
bool System>
369 using Type = Container;
370 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
371 using ElementMapping = CppToPg<ElementType>;
372 using Mapping = ArrayPgOid<Container, System>;
374 static Oid GetOid(
const UserTypes& types) {
375 return ElementMapping::GetArrayOid(types);
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>;
386 static constexpr Oid GetOid(
const UserTypes&) {
387 return static_cast<Oid>(ElementMapping::array_oid);
391template <
typename Container>
392constexpr bool IsElementMappedToSystem() {
393 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
396 return IsTypeMappedToSystem<
397 typename traits::ContainerFinalElement<Container>::type>();
401template <
typename Container>
402constexpr bool EnableArrayParser() {
403 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
406 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
407 return traits::kHasParser<ElementType>;
410template <
typename Container>
411inline constexpr bool kEnableArrayParser = EnableArrayParser<Container>();
413template <
typename Container>
414constexpr bool EnableArrayFormatter() {
415 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
418 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
419 return traits::kHasFormatter<ElementType>;
422template <
typename Container>
423inline constexpr bool kEnableArrayFormatter = EnableArrayFormatter<Container>();
432constexpr bool IsTypeMappedToSystemArray() {
433 return traits::kIsMappedToPg<T> &&
435 typename CppToPg<T>::Mapping,
436 io::detail::ArrayPgOid<
typename CppToPg<T>::Type,
true>>::value;
458template <
typename... T>
462template <
typename T, std::size_t Size>
466template <
typename... T>
470template <
typename... T>
481template <
typename Container>
482class ContainerChunk {
486 "Only containers explicitly declared as compatible are supported");
488 using value_type =
typename Container::value_type;
489 using const_iterator_type =
typename Container::const_iterator;
491 ContainerChunk(const_iterator_type begin, std::size_t size)
492 : begin_{begin}, end_{std::next(begin, size)}, size_{size} {}
494 std::size_t size()
const {
return size_; }
495 bool empty()
const {
return begin_ == end_; }
497 const_iterator_type begin()
const {
return begin_; }
498 const_iterator_type cbegin()
const {
return begin_; }
500 const_iterator_type end()
const {
return end_; }
501 const_iterator_type cend()
const {
return end_; }
504 const_iterator_type begin_;
505 const_iterator_type end_;
510template <
typename Container>
511class ContainerSplitter {
515 "Only containers explicitly declared as compatible are supported");
517 using value_type = ContainerChunk<Container>;
519 class ChunkIterator {
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},
527 static_cast<size_t>(std::distance(current, container_.end()))},
530 bool operator==(
const ChunkIterator& rhs)
const {
531 return current_ == rhs.current_;
534 bool operator!=(
const ChunkIterator& rhs)
const {
return !(*
this == rhs); }
536 value_type operator*()
const {
return {current_, NextStep()}; }
538 ChunkIterator& operator++() {
539 auto step = NextStep();
540 std::advance(current_, step);
545 ChunkIterator operator++(
int) {
546 ChunkIterator tmp{*
this};
551 std::size_t TailSize()
const {
return tail_size_; }
554 std::size_t NextStep()
const {
return std::min(chunk_size_, tail_size_); }
556 const Container& container_;
557 const std::size_t chunk_size_;
558 std::size_t tail_size_;
559 UnderlyingIterator current_;
562 ContainerSplitter(
const Container& container, std::size_t chunk_elements)
563 : container_{container}, chunk_size_{chunk_elements} {}
565 std::size_t size()
const {
566 auto sz = container_.size();
567 return sz / chunk_size_ + (sz % chunk_size_ ? 1 : 0);
569 bool empty()
const {
return container_.empty(); }
571 ChunkIterator begin()
const {
572 return {container_, container_.begin(), chunk_size_};
575 ChunkIterator end()
const {
576 return {container_, container_.end(), chunk_size_};
579 std::size_t ChunkSize()
const {
return chunk_size_; }
580 const Container& GetContainer()
const {
return container_; }
583 const Container& container_;
584 const std::size_t chunk_size_;
587template <
typename Container,
588 typename Seq = std::make_index_sequence<
589 boost::pfr::tuple_size_v<
typename Container::value_type>>>
590struct ColumnsSplitterHelper;
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");
598 template <std::size_t Index>
599 struct FieldProjection {
600 using RowType =
typename Container::value_type;
602 boost::pfr::tuple_element_t<Index,
typename Container::value_type>;
604 const FieldType& operator()(
const RowType& value)
const noexcept {
605 return boost::pfr::get<Index>(value);
609 template <std::size_t Index>
611 USERVER_NAMESPACE::utils::impl::ProjectingView<
const Container,
612 FieldProjection<Index>>;
614 template <
typename Fn>
615 static void Perform(
const Container& container, std::size_t chunk_elements,
617 DoSplitByChunks(chunk_elements, fn, FieldView<Indexes>{container}...);
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()...);
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...);
641template <
typename Container>
642class ContainerByColumnsSplitter final {
644 ContainerByColumnsSplitter(
const Container& container,
645 std::size_t chunk_elements)
646 : container_{container}, chunk_elements_{chunk_elements} {}
648 template <
typename Fn>
649 void Perform(
const Fn& fn) {
650 ColumnsSplitterHelper<Container>::Perform(container_, chunk_elements_, fn);
654 const Container& container_;
655 const std::size_t chunk_elements_;
661template <
typename Container>
665template <
typename Container,
typename Projection>
667 USERVER_NAMESPACE::utils::impl::ProjectingView<
const Container, Projection>>
671template <
typename Container>
672detail::ContainerSplitter<Container> SplitContainer(
673 const Container& container, std::size_t chunk_elements) {
674 return {container, chunk_elements};
677template <
typename Container>
678detail::ContainerByColumnsSplitter<Container> SplitContainerByColumns(
679 const Container& container, std::size_t chunk_elements) {
680 return {container, chunk_elements};