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 : std::conditional_t<
47 kIsFixedSizeContainer<T>,
48 detail::HasFixedDimensionsImpl<T>,
49 BoolConstant<!kIsCompatibleContainer<T>>>::type {};
51template <
typename Container>
52inline constexpr bool kHasFixedDimensions = HasFixedDimensions<Container>::value;
55struct FixedDimensions;
60template <
typename T, std::size_t N>
64inline constexpr std::size_t kDimensionSize = DimensionSize<T>::value;
68template <
typename A,
typename B>
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...>;
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;
85struct FixedDimensionsNonContainer {
86 using type = std::integer_sequence<std::size_t>;
91template <
typename T, T... Values>
92constexpr std::array<T,
sizeof...(Values)> MakeArray(
const std::integer_sequence<T, Values...>&) {
97struct FixedDimensions : std::conditional_t<
98 kIsFixedSizeContainer<T>,
99 detail::FixedDimensionsImpl<T>,
100 detail::FixedDimensionsNonContainer<T>> {};
106template <
typename Element>
107inline bool ForceInitElementMapping() {
109 if constexpr (
io::
traits::kIsMappedToPg<Element> || !
io::
traits::kIsCompositeType<Element>) {
110 return ForceReference(CppToPg<Element>::init_);
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>;
127 using BaseType::BaseType;
129 void operator()(
FieldBuffer buffer,
const TypeBufferCategory& categories) {
133 Integer dim_count{0};
135 if (dim_count !=
static_cast<Integer>(dimensions) && ForceInitElementMapping<ElementType>()) {
136 if (dim_count == 0) {
138 swap(
this->value, empty);
153 auto elem_category = GetTypeBufferCategory(categories, elem_oid);
156 Dimensions on_the_wire;
157 for (
auto& dim : on_the_wire) {
165 if (!CheckDimensions(on_the_wire)) {
170 ReadDimension(buffer, on_the_wire.begin(), elem_category, categories, tmp);
171 swap(
this->value, tmp);
175 bool CheckDimensions(
const Dimensions& dims)
const {
176 if constexpr (
traits::kHasFixedDimensions<Container>) {
177 return dims ==
traits::MakeArray(
typename traits::FixedDimensions<Container>::type{});
179 return CheckDimensions<ValueType>(dims.begin());
181 template <
typename Element>
182 bool CheckDimensions([[maybe_unused]] DimensionConstIterator dim)
const {
183 if constexpr (
traits::kIsFixedSizeContainer<Element>) {
185 if (*dim !=
traits::kDimensionSize<Element>) {
188 if constexpr (
traits::kDimensionCount<Element> == 1) {
191 return CheckDimensions<
typename Element::value_type>(dim + 1);
193 }
else if constexpr (
traits::kIsCompatibleContainer<Element>) {
194 if constexpr (
traits::kDimensionCount<Element> == 1) {
197 return CheckDimensions<
typename Element::value_type>(dim + 1);
203 template <
typename T>
204 auto GetInserter(T& value) {
205 return std::inserter(value, value.end());
208 template <
typename T, std::size_t n>
209 auto GetInserter(std::array<T, n>& array) {
210 return array.begin();
213 template <
typename Element>
216 DimensionConstIterator dim,
218 const TypeBufferCategory& categories,
221 if constexpr (
traits::kIsCompatibleContainer<Element>) {
222 if constexpr (
traits::kCanClear<Element>) {
225 if constexpr (
traits::kCanReserve<Element>) {
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>) {
233 ReadDimension(buffer, dim + 1, elem_category, categories, val);
235 buffer.ReadRaw(val, categories, elem_category);
237 *it++ = std::move(val);
244 DimensionConstIterator dim,
246 const TypeBufferCategory& categories,
247 std::vector<
bool>& elem
251 auto value = elem.begin();
252 for (std::size_t i = 0; i < *dim; ++i) {
254 buffer.ReadRaw(val, categories, elem_category);
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;
272 using BaseType::BaseType;
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);
283 if (
this->value.empty()) {
284 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
285 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
286 io::WriteBuffer(types, buffer,
static_cast<Integer>(elem_type_oid));
291 io::WriteBuffer(types, buffer,
static_cast<Integer>(dimensions));
293 io::WriteBuffer(types, buffer,
static_cast<Integer>(0));
295 io::WriteBuffer(types, buffer,
static_cast<Integer>(elem_type_oid));
296 Dimensions dims = GetDimensions();
298 WriteDimensionData(types, buffer, dims);
300 WriteData(types, dims.begin(), buffer,
this->value);
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());
313 Dimensions GetDimensions()
const {
314 if constexpr (
traits::kHasFixedDimensions<Container>) {
315 return traits::MakeArray(
typename traits::FixedDimensions<Container>::type{});
318 CalculateDimensions(dims.begin(),
this->value);
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));
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()) {
335 if constexpr (1 <
traits::kDimensionCount<Element>) {
337 for (
const auto& sub : element) {
338 WriteData(types, dim + 1, buffer, sub);
342 for (
const auto& sub : element) {
343 io::WriteRawBinary(types, buffer, sub);
348 template <
typename Buffer>
349 void WriteData(
const UserTypes& types, DimensionConstIterator dim, Buffer& buffer,
const std::vector<
bool>& element)
351 if (*dim != element.size()) {
352 throw InvalidDimensions{*dim, element.size()};
354 for (
bool sub : element) {
355 io::WriteRawBinary(types, buffer, sub);
360template <
typename Container,
bool System>
362 using Type = Container;
363 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
364 using ElementMapping = CppToPg<ElementType>;
365 using Mapping = ArrayPgOid<Container, System>;
367 static Oid GetOid(
const UserTypes& types) {
return ElementMapping::GetArrayOid(types); }
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>;
377 static constexpr Oid GetOid(
const UserTypes&) {
return static_cast<Oid>(ElementMapping::array_oid); }
380template <
typename Container>
381constexpr bool IsElementMappedToSystem() {
382 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
385 return IsTypeMappedToSystem<
typename traits::ContainerFinalElement<Container>::type>();
389template <
typename Container>
390constexpr bool EnableArrayParser() {
391 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
394 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
395 return traits::kHasParser<ElementType>;
398template <
typename Container>
399inline constexpr bool kEnableArrayParser = EnableArrayParser<Container>();
401template <
typename Container>
402constexpr bool EnableArrayFormatter() {
403 if constexpr (!
traits::kIsCompatibleContainer<Container>) {
406 using ElementType =
typename traits::ContainerFinalElement<Container>::type;
407 return traits::kHasFormatter<ElementType>;
410template <
typename Container>
411inline constexpr bool kEnableArrayFormatter = EnableArrayFormatter<Container>();
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;
442template <
typename... T>
446template <
typename T, std::size_t Size>
450template <
typename... T>
454template <
typename... T>
465template <
typename Container>
466class ContainerChunk {
470 "Only containers explicitly declared as compatible are supported"
473 using value_type =
typename Container::value_type;
474 using const_iterator_type =
typename Container::const_iterator;
476 ContainerChunk(const_iterator_type begin, std::size_t size)
477 : begin_{begin}, end_{std::next(begin, size)}, size_{size} {}
479 std::size_t size()
const {
return size_; }
480 bool empty()
const {
return begin_ == end_; }
482 const_iterator_type begin()
const {
return begin_; }
483 const_iterator_type cbegin()
const {
return begin_; }
485 const_iterator_type end()
const {
return end_; }
486 const_iterator_type cend()
const {
return end_; }
489 const_iterator_type begin_;
490 const_iterator_type end_;
495template <
typename Container>
496class ContainerSplitter {
500 "Only containers explicitly declared as compatible are supported"
503 using value_type = ContainerChunk<Container>;
505 class ChunkIterator {
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()))},
514 bool operator==(
const ChunkIterator& rhs)
const {
return current_ == rhs.current_; }
516 bool operator!=(
const ChunkIterator& rhs)
const {
return !(*
this == rhs); }
518 value_type operator*()
const {
return {current_, NextStep()}; }
520 ChunkIterator& operator++() {
521 auto step = NextStep();
522 std::advance(current_, step);
527 ChunkIterator operator++(
int) {
528 ChunkIterator tmp{*
this};
533 std::size_t TailSize()
const {
return tail_size_; }
536 std::size_t NextStep()
const {
return std::min(chunk_size_, tail_size_); }
538 const Container& container_;
539 const std::size_t chunk_size_;
540 std::size_t tail_size_;
541 UnderlyingIterator current_;
544 ContainerSplitter(
const Container& container, std::size_t chunk_elements)
545 : container_{container}, chunk_size_{chunk_elements} {}
547 std::size_t size()
const {
548 auto sz = container_.size();
549 return sz / chunk_size_ + (sz % chunk_size_ ? 1 : 0);
551 bool empty()
const {
return container_.empty(); }
553 ChunkIterator begin()
const {
return {container_, container_.begin(), chunk_size_}; }
555 ChunkIterator end()
const {
return {container_, container_.end(), chunk_size_}; }
557 std::size_t ChunkSize()
const {
return chunk_size_; }
558 const Container& GetContainer()
const {
return container_; }
561 const Container& container_;
562 const std::size_t chunk_size_;
567 typename Seq = std::make_index_sequence<boost::pfr::tuple_size_v<
typename Container::value_type>>>
568struct ColumnsSplitterHelper;
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");
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>;
580 const FieldType& operator()(
const RowType& value)
const noexcept {
return boost::pfr::get<Index>(value); }
583 template <std::size_t Index>
584 using FieldView = USERVER_NAMESPACE::
utils::impl::ProjectingView<
const Container, FieldProjection<Index>>;
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}...);
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()...);
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...);
609template <
typename Container>
610class ContainerByColumnsSplitter final {
612 ContainerByColumnsSplitter(
const Container& container, std::size_t chunk_elements)
613 : container_{container}, chunk_elements_{chunk_elements} {}
615 template <
typename Fn>
616 void Perform(
const Fn& fn) {
617 ColumnsSplitterHelper<Container>::Perform(container_, chunk_elements_, fn);
621 const Container& container_;
622 const std::size_t chunk_elements_;
628template <
typename Container>
631template <
typename Container,
typename Projection>
636template <
typename Container>
637detail::ContainerSplitter<Container> SplitContainer(
const Container& container, std::size_t chunk_elements) {
638 return {container, chunk_elements};
641template <
typename Container>
642detail::ContainerByColumnsSplitter<Container>
643SplitContainerByColumns(
const Container& container, std::size_t chunk_elements) {
644 return {container, chunk_elements};