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
59template <
typename Container>
60struct HasFixedDimensions;
64template <
typename Container>
65struct HasFixedDimensionsImpl {
67 typename HasFixedDimensions<
typename Container::value_type>::type;
73struct HasFixedDimensions
74 : std::conditional_t<kIsFixedSizeContainer<T>,
75 detail::HasFixedDimensionsImpl<T>,
76 BoolConstant<!kIsCompatibleContainer<T>>>::type {};
78template <
typename Container>
79inline constexpr bool kHasFixedDimensions =
80 HasFixedDimensions<Container>::value;
83struct FixedDimensions;
88template <
typename T, std::size_t N>
93inline constexpr std::size_t kDimensionSize = DimensionSize<T>::value;
97template <
typename A,
typename B>
100template <
typename T, T... U, T... V>
101struct JoinSequences<std::integer_sequence<T, U...>,
102 std::integer_sequence<T, V...>> {
103 using type = std::integer_sequence<T, U..., V...>;
107struct FixedDimensionsImpl {
108 static_assert(kIsFixedSizeContainer<T>,
"Container must have fixed size");
109 using type =
typename JoinSequences<
110 std::integer_sequence<std::size_t, kDimensionSize<T>>,
111 typename FixedDimensions<
typename T::value_type>::type>::type;
115struct FixedDimensionsNonContainer {
116 using type = std::integer_sequence<std::size_t>;
121template <
typename T, T... Values>
122constexpr std::array<T,
sizeof...(Values)> MakeArray(
123 const std::integer_sequence<T, Values...>&) {
128struct FixedDimensions
129 : std::conditional_t<kIsFixedSizeContainer<T>,
130 detail::FixedDimensionsImpl<T>,
131 detail::FixedDimensionsNonContainer<T>> {};
137template <
typename Element>
138inline bool ForceInitElementMapping() {
140 if constexpr (
io::
traits::kIsMappedToPg<Element> ||
141 !
io::
traits::kIsCompositeType<Element>) {
142 return ForceReference(CppToPg<Element>::init_);
148template <
typename Container>
149struct ArrayBinaryParser : BufferParserBase<Container> {
150 using BaseType = BufferParserBase<Container>;
151 using ValueType =
typename BaseType::ValueType;
152 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
153 constexpr static std::size_t dimensions =
traits::kDimensionCount<Container>;
154 using Dimensions = std::array<std::size_t, dimensions>;
155 using DimensionIterator =
typename Dimensions::iterator;
156 using DimensionConstIterator =
typename Dimensions::const_iterator;
157 using ElementMapping = CppToPg<ElementType>;
159 using BaseType::BaseType;
161 void operator()(
FieldBuffer buffer,
const TypeBufferCategory& categories) {
165 Integer dim_count{0};
167 if (dim_count !=
static_cast<Integer>(dimensions) &&
168 ForceInitElementMapping<ElementType>()) {
169 if (dim_count == 0) {
171 swap(
this->value, empty);
186 auto elem_category = GetTypeBufferCategory(categories, elem_oid);
189 Dimensions on_the_wire;
190 for (
auto& dim : on_the_wire) {
198 if (!CheckDimensions(on_the_wire)) {
203 ReadDimension(buffer, on_the_wire.begin(), elem_category, categories, tmp);
204 swap(
this->value, tmp);
208 bool CheckDimensions(
const Dimensions& dims)
const {
209 if constexpr (
traits::kHasFixedDimensions<Container>) {
210 return dims ==
traits::MakeArray(
211 typename traits::FixedDimensions<Container>::type{});
213 return CheckDimensions<ValueType>(dims.begin());
215 template <
typename Element>
216 bool CheckDimensions([[maybe_unused]] DimensionConstIterator dim)
const {
217 if constexpr (
traits::kIsFixedSizeContainer<Element>) {
219 if (*dim !=
traits::kDimensionSize<Element>) {
222 if constexpr (
traits::kDimensionCount<Element> == 1) {
225 return CheckDimensions<
typename Element::value_type>(dim + 1);
227 }
else if constexpr (
traits::kIsCompatibleContainer<Element>) {
228 if constexpr (
traits::kDimensionCount<Element> == 1) {
231 return CheckDimensions<
typename Element::value_type>(dim + 1);
237 template <
typename T>
238 auto GetInserter(T& value) {
239 return std::inserter(value, value.end());
242 template <
typename T, std::size_t n>
243 auto GetInserter(std::array<T, n>& array) {
244 return array.begin();
247 template <
typename Element>
248 void ReadDimension(
FieldBuffer& buffer, DimensionConstIterator dim,
250 const TypeBufferCategory& categories, Element& elem) {
251 if constexpr (
traits::kIsCompatibleContainer<Element>) {
252 if constexpr (
traits::kCanClear<Element>) {
255 if constexpr (
traits::kCanReserve<Element>) {
258 auto it = GetInserter(elem);
259 for (std::size_t i = 0; i < *dim; ++i) {
260 typename Element::value_type val;
261 if constexpr (1 <
traits::kDimensionCount<Element>) {
263 ReadDimension(buffer, dim + 1, elem_category, categories, val);
265 buffer.ReadRaw(val, categories, elem_category);
267 *it++ = std::move(val);
272 void ReadDimension(
FieldBuffer& buffer, DimensionConstIterator dim,
274 const TypeBufferCategory& categories,
275 std::vector<
bool>& elem) {
278 auto value = elem.begin();
279 for (std::size_t i = 0; i < *dim; ++i) {
281 buffer.ReadRaw(val, categories, elem_category);
287template <
typename Container>
288struct ArrayBinaryFormatter : BufferFormatterBase<Container> {
289 using BaseType = BufferFormatterBase<Container>;
290 using ValueType =
typename BaseType::ValueType;
291 using ArrayMapping = CppToPg<Container>;
292 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
293 using ElementMapping = CppToPg<ElementType>;
294 constexpr static std::size_t dimensions =
traits::kDimensionCount<Container>;
295 using Dimensions = std::array<std::size_t, dimensions>;
296 using DimensionIterator =
typename Dimensions::iterator;
297 using DimensionConstIterator =
typename Dimensions::const_iterator;
299 using BaseType::BaseType;
302 template <
typename Buffer>
303 void operator()(
const UserTypes& types, Buffer& buffer,
304 Oid replace_oid = kInvalidOid)
const {
305 auto elem_type_oid = ElementMapping::GetOid(types);
306 if (replace_oid != kInvalidOid &&
307 replace_oid != ArrayMapping::GetOid(types)) {
308 elem_type_oid = types.FindElementOid(replace_oid);
312 if (
this->value.empty()) {
313 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
314 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
315 io::WriteBuffer(types, buffer,
static_cast<Integer>(elem_type_oid));
320 io::WriteBuffer(types, buffer,
static_cast<Integer>(dimensions));
322 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
324 io::WriteBuffer(types, buffer,
static_cast<Integer>(elem_type_oid));
325 Dimensions dims = GetDimensions();
327 WriteDimensionData(types, buffer, dims);
329 WriteData(types, dims.begin(), buffer,
this->value);
333 template <
typename Element>
334 void CalculateDimensions([[maybe_unused]] DimensionIterator dim,
335 const Element& element)
const {
336 if constexpr (
traits::kIsCompatibleContainer<Element>) {
337 *dim = element.size();
338 if (!element.empty()) {
339 CalculateDimensions(dim + 1, *element.begin());
343 Dimensions GetDimensions()
const {
344 if constexpr (
traits::kHasFixedDimensions<Container>) {
346 typename traits::FixedDimensions<Container>::type{});
349 CalculateDimensions(dims.begin(),
this->value);
353 template <
typename Buffer>
354 void WriteDimensionData(
const UserTypes& types, Buffer& buffer,
355 const Dimensions& dims)
const {
356 for (
auto dim : dims) {
357 io::WriteBuffer(types, buffer,
static_cast<Integer>(dim));
358 io::WriteBuffer(types, buffer,
static_cast<Integer>(1));
362 template <
typename Buffer,
typename Element>
363 void WriteData(
const UserTypes& types, DimensionConstIterator dim,
364 Buffer& buffer,
const Element& element)
const {
365 if (*dim != element.size()) {
368 if constexpr (1 <
traits::kDimensionCount<Element>) {
370 for (
const auto& sub : element) {
371 WriteData(types, dim + 1, buffer, sub);
375 for (
const auto& sub : element) {
376 io::WriteRawBinary(types, buffer, sub);
381 template <
typename Buffer>
382 void WriteData(
const UserTypes& types, DimensionConstIterator dim,
383 Buffer& buffer,
const std::vector<
bool>& element)
const {
384 if (*dim != element.size()) {
385 throw InvalidDimensions{*dim, element.size()};
387 for (
bool sub : element) {
388 io::WriteRawBinary(types, buffer, sub);
393template <
typename Container,
bool System>
395 using Type = Container;
396 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
397 using ElementMapping = CppToPg<ElementType>;
398 using Mapping = ArrayPgOid<Container, System>;
400 static Oid GetOid(
const UserTypes& types) {
401 return ElementMapping::GetArrayOid(types);
405template <
typename Container>
406struct ArrayPgOid<Container,
true> {
407 using Type = Container;
408 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
409 using ElementMapping = CppToPg<ElementType>;
410 using Mapping = ArrayPgOid<Container,
true>;
412 static constexpr Oid GetOid(
const UserTypes&) {
413 return static_cast<Oid>(ElementMapping::array_oid);
417template <
typename Container>
418constexpr bool IsElementMappedToSystem() {
419 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
422 return IsTypeMappedToSystem<
423 typename traits::ContainerFinalElement<Container>::type>();
427template <
typename Container>
428constexpr bool EnableArrayParser() {
429 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
432 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
433 return traits::kHasParser<ElementType>;
436template <
typename Container>
437inline constexpr bool kEnableArrayParser = EnableArrayParser<Container>();
439template <
typename Container>
440constexpr bool EnableArrayFormatter() {
441 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
444 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
445 return traits::kHasFormatter<ElementType>;
448template <
typename Container>
449inline constexpr bool kEnableArrayFormatter = EnableArrayFormatter<Container>();
458constexpr bool IsTypeMappedToSystemArray() {
459 return traits::kIsMappedToPg<T> &&
461 typename CppToPg<T>::Mapping,
462 io::detail::ArrayPgOid<
typename CppToPg<T>::Type,
true>>::value;
484template <
typename... T>
488template <
typename T, std::size_t Size>
492template <
typename... T>
496template <
typename... T>
507template <
typename Container>
508class ContainerChunk {
512 "Only containers explicitly declared as compatible are supported");
514 using value_type =
typename Container::value_type;
515 using const_iterator_type =
typename Container::const_iterator;
517 ContainerChunk(const_iterator_type begin, std::size_t size)
518 : begin_{begin}, end_{std::next(begin, size)}, size_{size} {}
520 std::size_t size()
const {
return size_; }
521 bool empty()
const {
return begin_ == end_; }
523 const_iterator_type begin()
const {
return begin_; }
524 const_iterator_type cbegin()
const {
return begin_; }
526 const_iterator_type end()
const {
return end_; }
527 const_iterator_type cend()
const {
return end_; }
530 const_iterator_type begin_;
531 const_iterator_type end_;
536template <
typename Container>
537class ContainerSplitter {
541 "Only containers explicitly declared as compatible are supported");
543 using value_type = ContainerChunk<Container>;
545 class ChunkIterator {
547 using UnderlyingIterator =
typename Container::const_iterator;
548 ChunkIterator(
const Container& container, UnderlyingIterator current,
549 std::size_t chunk_elements)
550 : container_{container},
551 chunk_size_{chunk_elements},
553 static_cast<size_t>(std::distance(current, container_.end()))},
556 bool operator==(
const ChunkIterator& rhs)
const {
557 return current_ == rhs.current_;
560 bool operator!=(
const ChunkIterator& rhs)
const {
return !(*
this == rhs); }
562 value_type operator*()
const {
return {current_, NextStep()}; }
564 ChunkIterator& operator++() {
565 auto step = NextStep();
566 std::advance(current_, step);
571 ChunkIterator operator++(
int) {
572 ChunkIterator tmp{*
this};
577 std::size_t TailSize()
const {
return tail_size_; }
580 std::size_t NextStep()
const {
return std::min(chunk_size_, tail_size_); }
582 const Container& container_;
583 const std::size_t chunk_size_;
584 std::size_t tail_size_;
585 UnderlyingIterator current_;
588 ContainerSplitter(
const Container& container, std::size_t chunk_elements)
589 : container_{container}, chunk_size_{chunk_elements} {}
591 std::size_t size()
const {
592 auto sz = container_.size();
593 return sz / chunk_size_ + (sz % chunk_size_ ? 1 : 0);
595 bool empty()
const {
return container_.empty(); }
597 ChunkIterator begin()
const {
598 return {container_, container_.begin(), chunk_size_};
601 ChunkIterator end()
const {
602 return {container_, container_.end(), chunk_size_};
605 std::size_t ChunkSize()
const {
return chunk_size_; }
606 const Container& GetContainer()
const {
return container_; }
609 const Container& container_;
610 const std::size_t chunk_size_;
613template <
typename Container,
614 typename Seq = std::make_index_sequence<
615 boost::pfr::tuple_size_v<
typename Container::value_type>>>
616struct ColumnsSplitterHelper;
619template <
typename Container, std::size_t... Indexes>
620struct ColumnsSplitterHelper<Container, std::index_sequence<Indexes...>> final {
621 static_assert(
sizeof...(Indexes) > 0,
622 "The aggregate having 0 fields doesn't make sense");
624 template <std::size_t Index>
625 struct FieldProjection {
626 using RowType =
typename Container::value_type;
628 boost::pfr::tuple_element_t<Index,
typename Container::value_type>;
630 const FieldType& operator()(
const RowType& value)
const noexcept {
631 return boost::pfr::get<Index>(value);
635 template <std::size_t Index>
637 USERVER_NAMESPACE::
utils::impl::ProjectingView<
const Container,
638 FieldProjection<Index>>;
640 template <
typename Fn>
641 static void Perform(
const Container& container, std::size_t chunk_elements,
643 DoSplitByChunks(chunk_elements, fn, FieldView<Indexes>{container}...);
647 template <
typename Fn,
typename... Views>
648 static void DoSplitByChunks(std::size_t chunk_elements,
const Fn& fn,
649 const Views&... views) {
650 DoIterateByChunks(fn, ContainerSplitter{views, chunk_elements}.begin()...);
653 template <
typename Fn,
typename FirstChunkIterator,
654 typename... ChunkIterators>
655 static void DoIterateByChunks(
const Fn& fn, FirstChunkIterator first,
656 ChunkIterators... chunks) {
657 while (first.TailSize() > 0) {
658 fn(*first, *chunks...);
667template <
typename Container>
668class ContainerByColumnsSplitter final {
670 ContainerByColumnsSplitter(
const Container& container,
671 std::size_t chunk_elements)
672 : container_{container}, chunk_elements_{chunk_elements} {}
674 template <
typename Fn>
675 void Perform(
const Fn& fn) {
676 ColumnsSplitterHelper<Container>::Perform(container_, chunk_elements_, fn);
680 const Container& container_;
681 const std::size_t chunk_elements_;
687template <
typename Container>
691template <
typename Container,
typename Projection>
693 USERVER_NAMESPACE::
utils::impl::ProjectingView<
const Container, Projection>>
697template <
typename Container>
698detail::ContainerSplitter<Container> SplitContainer(
699 const Container& container, std::size_t chunk_elements) {
700 return {container, chunk_elements};
703template <
typename Container>
704detail::ContainerByColumnsSplitter<Container> SplitContainerByColumns(
705 const Container& container, std::size_t chunk_elements) {
706 return {container, chunk_elements};