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 {
40 using type =
typename HasFixedDimensions<
typename Container::value_type>::type;
46struct HasFixedDimensions
48 kIsFixedSizeContainer<T>,
49 detail::HasFixedDimensionsImpl<T>,
50 BoolConstant<!kIsCompatibleContainer<T>>>::type {};
52template <
typename Container>
53inline constexpr bool kHasFixedDimensions = HasFixedDimensions<Container>::value;
56struct FixedDimensions;
61template <
typename T, std::size_t N>
62struct DimensionSize<std::array<T, N>> : std::integral_constant<std::size_t, N> {};
65inline constexpr std::size_t kDimensionSize = DimensionSize<T>::value;
69template <
typename A,
typename B>
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...>;
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;
86struct FixedDimensionsNonContainer {
87 using type = std::integer_sequence<std::size_t>;
92template <
typename T, T... Values>
93constexpr std::array<T,
sizeof...(Values)> MakeArray(
const std::integer_sequence<T, Values...>&) {
100 kIsFixedSizeContainer<T>,
101 detail::FixedDimensionsImpl<T>,
102 detail::FixedDimensionsNonContainer<T>> {};
108template <
typename Element>
109inline bool ForceInitElementMapping() {
111 if constexpr (io::
traits::kIsMappedToPg<Element> || !io::
traits::kIsCompositeType<Element>) {
112 return ForceReference(CppToPg<Element>::init);
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>;
129 using BaseType::BaseType;
131 void operator()(
FieldBuffer buffer,
const TypeBufferCategory& categories) {
135 Integer dim_count{0};
137 if (dim_count !=
static_cast<Integer>(dimensions) && ForceInitElementMapping<ElementType>()) {
138 if (dim_count == 0) {
140 swap(
this->value, empty);
155 auto elem_category = GetTypeBufferCategory(categories, elem_oid);
158 Dimensions on_the_wire;
159 for (
auto& dim : on_the_wire) {
167 if (!CheckDimensions(on_the_wire)) {
172 ReadDimension(buffer, on_the_wire.begin(), elem_category, categories, tmp);
173 swap(
this->value, tmp);
177 bool CheckDimensions(
const Dimensions& dims)
const {
178 if constexpr (
traits::kHasFixedDimensions<Container>) {
179 return dims ==
traits::MakeArray(
typename traits::FixedDimensions<Container>::type{});
181 return CheckDimensions<ValueType>(dims.begin());
183 template <
typename Element>
184 bool CheckDimensions([[maybe_unused]] DimensionConstIterator dim)
const {
185 if constexpr (
traits::kIsFixedSizeContainer<Element>) {
187 if (*dim !=
traits::kDimensionSize<Element>) {
190 if constexpr (
traits::kDimensionCount<Element> == 1) {
193 return CheckDimensions<
typename Element::value_type>(dim + 1);
195 }
else if constexpr (
traits::kIsCompatibleContainer<Element>) {
196 if constexpr (
traits::kDimensionCount<Element> == 1) {
199 return CheckDimensions<
typename Element::value_type>(dim + 1);
205 template <
typename T>
206 auto GetInserter(T& value) {
207 return std::inserter(value, value.end());
210 template <
typename T, std::size_t N>
211 auto GetInserter(std::array<T, N>& array) {
212 return array.begin();
215 template <
typename Element>
218 DimensionConstIterator dim,
220 const TypeBufferCategory& categories,
223 if constexpr (
traits::kIsCompatibleContainer<Element>) {
224 if constexpr (
traits::kCanClear<Element>) {
227 if constexpr (
traits::kCanReserve<Element>) {
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>) {
235 ReadDimension(buffer, dim + 1, elem_category, categories, val);
237 buffer.ReadRaw(val, categories, elem_category);
239 *it++ = std::move(val);
246 DimensionConstIterator dim,
248 const TypeBufferCategory& categories,
249 std::vector<
bool>& elem
253 auto value = elem.begin();
254 for (std::size_t i = 0; i < *dim; ++i) {
256 buffer.ReadRaw(val, categories, elem_category);
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;
274 using BaseType::BaseType;
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)) {
285 if (
this->value.empty()) {
286 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
287 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
288 io::WriteBuffer(types, buffer,
static_cast<Integer>(elem_type_oid));
293 io::WriteBuffer(types, buffer,
static_cast<Integer>(dimensions));
295 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
297 io::WriteBuffer(types, buffer,
static_cast<Integer>(elem_type_oid));
298 Dimensions dims = GetDimensions();
300 WriteDimensionData(types, buffer, dims);
302 WriteData(types, dims.begin(), buffer,
this->value);
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());
315 Dimensions GetDimensions()
const {
316 if constexpr (
traits::kHasFixedDimensions<Container>) {
317 return traits::MakeArray(
typename traits::FixedDimensions<Container>::type{});
320 CalculateDimensions(dims.begin(),
this->value);
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));
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()) {
337 if constexpr (1 <
traits::kDimensionCount<Element>) {
339 for (
const auto& sub : element) {
340 WriteData(types, dim + 1, buffer, sub);
344 for (
const auto& sub : element) {
345 io::WriteRawBinary(types, buffer, sub);
350 template <
typename Buffer>
351 void WriteData(
const UserTypes& types, DimensionConstIterator dim, Buffer& buffer,
const std::vector<
bool>& element)
353 if (*dim != element.size()) {
356 for (
const bool sub : element) {
357 io::WriteRawBinary(types, buffer, sub);
362template <
typename Container,
bool System>
364 using Type = Container;
365 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
366 using ElementMapping = CppToPg<ElementType>;
367 using Mapping = ArrayPgOid<Container, System>;
369 static Oid GetOid(
const UserTypes& types) {
return ElementMapping::GetArrayOid(types); }
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>;
379 static constexpr Oid GetOid(
const UserTypes&) {
return static_cast<Oid>(ElementMapping::array_oid); }
382template <
typename Container>
383constexpr bool IsElementMappedToSystem() {
384 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
387 return IsTypeMappedToSystem<
typename traits::ContainerFinalElement<Container>::type>();
391template <
typename Container>
392constexpr bool EnableArrayParser() {
393 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
396 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
397 return traits::kHasParser<ElementType>;
400template <
typename Container>
401inline constexpr bool kEnableArrayParser = EnableArrayParser<Container>();
403template <
typename Container>
404constexpr bool EnableArrayFormatter() {
405 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
408 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
409 return traits::kHasFormatter<ElementType>;
412template <
typename Container>
413inline constexpr bool kEnableArrayFormatter = EnableArrayFormatter<Container>();
418struct CppToPg<T, std::enable_if_t<
traits::detail::EnableContainerMapping<T>()>>
419 : detail::ArrayPgOid<T, detail::IsElementMappedToSystem<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;
430struct Input<T, std::enable_if_t<!detail::kCustomParserDefined<T> && io::detail::kEnableArrayParser<T>>> {
431 using type = io::detail::ArrayBinaryParser<T>;
435struct Output<T, std::enable_if_t<!detail::kCustomFormatterDefined<T> && io::detail::kEnableArrayFormatter<T>>> {
436 using type = io::detail::ArrayBinaryFormatter<T>;
444template <
typename... T>
448template <
typename T, std::size_t Size>
452template <
typename... T>
456template <
typename... T>
467template <
typename Container>
468class ContainerChunk {
472 "Only containers explicitly declared as compatible are supported"
475 using value_type =
typename Container::value_type;
476 using const_iterator_type =
typename Container::const_iterator;
478 ContainerChunk(const_iterator_type begin, std::size_t size)
480 end_{std::next(begin, size)},
484 std::size_t size()
const {
return size_; }
485 bool empty()
const {
return begin_ == end_; }
487 const_iterator_type begin()
const {
return begin_; }
488 const_iterator_type cbegin()
const {
return begin_; }
490 const_iterator_type end()
const {
return end_; }
491 const_iterator_type cend()
const {
return end_; }
494 const_iterator_type begin_;
495 const_iterator_type end_;
500template <
typename Container>
501class ContainerSplitter {
505 "Only containers explicitly declared as compatible are supported"
508 using value_type = ContainerChunk<Container>;
510 class ChunkIterator {
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()))},
520 bool operator==(
const ChunkIterator& rhs)
const {
return current_ == rhs.current_; }
522 bool operator!=(
const ChunkIterator& rhs)
const {
return !(*
this == rhs); }
524 value_type operator*()
const {
return {current_, NextStep()}; }
526 ChunkIterator& operator++() {
527 auto step = NextStep();
528 std::advance(current_, step);
533 ChunkIterator operator++(
int) {
534 ChunkIterator tmp{*
this};
539 std::size_t TailSize()
const {
return tail_size_; }
542 std::size_t NextStep()
const {
return std::min(chunk_size_, tail_size_); }
544 const Container& container_;
545 const std::size_t chunk_size_;
546 std::size_t tail_size_;
547 UnderlyingIterator current_;
550 ContainerSplitter(
const Container& container, std::size_t chunk_elements)
551 : container_{container},
552 chunk_size_{chunk_elements}
555 std::size_t size()
const {
556 auto sz = container_.size();
557 return sz / chunk_size_ + (sz % chunk_size_ ? 1 : 0);
559 bool empty()
const {
return container_.empty(); }
561 ChunkIterator begin()
const {
return {container_, container_.begin(), chunk_size_}; }
563 ChunkIterator end()
const {
return {container_, container_.end(), chunk_size_}; }
565 std::size_t ChunkSize()
const {
return chunk_size_; }
566 const Container& GetContainer()
const {
return container_; }
569 const Container& container_;
570 const std::size_t chunk_size_;
575 typename Seq = std::make_index_sequence<boost::pfr::tuple_size_v<
typename Container::value_type>>>
576struct ColumnsSplitterHelper;
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");
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>;
588 const FieldType& operator()(
const RowType& value)
const noexcept {
return boost::pfr::get<Index>(value); }
591 template <std::size_t Index>
592 using FieldView = USERVER_NAMESPACE::utils::
impl::ProjectingView<
const Container, FieldProjection<Index>>;
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}...);
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()...);
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...);
618 typename Seq = std::make_index_sequence<boost::pfr::tuple_size_v<
typename Container::value_type>>>
619struct ColumnsDecomposerHelper;
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");
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>;
630 const FieldType& operator()(
const RowType& value)
const noexcept {
return boost::pfr::get<Index>(value); }
633 template <std::size_t Index>
634 using FieldView = USERVER_NAMESPACE::utils::
impl::ProjectingView<
const Container, FieldProjection<Index>>;
636 template <
typename Fn>
637 static auto Perform(
const Container& container,
const Fn& fn) {
638 return fn(FieldView<Indexes>{container}...);
643template <
typename Container>
644class ContainerByColumnsSplitter
final {
646 ContainerByColumnsSplitter(
const Container& container, std::size_t chunk_elements)
647 : container_{container},
648 chunk_elements_{chunk_elements}
651 template <
typename Fn>
652 void Perform(
const Fn& fn) {
653 ColumnsSplitterHelper<Container>::Perform(container_, chunk_elements_, fn);
657 const Container& container_;
658 const std::size_t chunk_elements_;
661template <
typename Container>
662class ContainerByColumnsDecomposer
final {
664 ContainerByColumnsDecomposer(
const Container& container)
665 : container_(container)
668 template <
typename Fn>
669 auto Perform(
const Fn& fn) {
670 return ColumnsDecomposerHelper<Container>::Perform(container_, fn);
674 const Container& container_;
680template <
typename Container>
683template <
typename Container,
typename Projection>
688template <
typename Container>
689detail::ContainerSplitter<Container> SplitContainer(
const Container& container, std::size_t chunk_elements) {
690 return {container, chunk_elements};
693template <
typename Container>
694detail::ContainerByColumnsSplitter<Container> SplitContainerByColumns(
695 const Container& container,
696 std::size_t chunk_elements
698 return {container, chunk_elements};
701template <
typename Container>
702detail::ContainerByColumnsDecomposer<Container> DecomposeContainerByColumns(
const Container& container) {