userver: userver/storages/mysql/impl/io/extractor.hpp Source File
Loading...
Searching...
No Matches
extractor.hpp
1#pragma once
2
3#include <vector>
4
5#include <userver/utils/meta.hpp>
6
7#include <userver/storages/mysql/impl/io/result_binder.hpp>
8
9#include <userver/storages/mysql/convert.hpp>
10
11USERVER_NAMESPACE_BEGIN
12
13namespace storages::mysql::impl::io {
14
15template <typename Container>
16class InPlaceStorage final {
17 public:
18 explicit InPlaceStorage(ResultBinder& binder);
19
20 void Reserve(std::size_t size);
21
22 template <typename ExtractionTag>
23 impl::bindings::OutputBindings& BindNextRow();
24
25 void CommitLastRow();
26
27 void RollbackLastRow();
28
29 Container&& ExtractData();
30
31 private:
32 Container data_;
33 ResultBinder& binder_;
34};
35
36template <typename Container, typename MapFrom>
37class ProxyingStorage final {
38 public:
39 explicit ProxyingStorage(ResultBinder& binder);
40
41 void Reserve(std::size_t size);
42
43 template <typename ExtractionTag>
44 impl::bindings::OutputBindings& BindNextRow();
45
46 void CommitLastRow();
47
48 void RollbackLastRow();
49
50 Container&& ExtractData();
51
52 private:
53 static constexpr bool kIsMapped =
54 !std::is_same_v<typename Container::value_type, MapFrom>;
55
56 MapFrom row_;
57 Container data_;
58 ResultBinder& binder_;
59};
60
61class ExtractorBase {
62 public:
63 ExtractorBase(std::size_t size);
64
65 virtual void Reserve(std::size_t size) = 0;
66
67 virtual OutputBindingsFwd& BindNextRow() = 0;
68 virtual void CommitLastRow() = 0;
69 virtual void RollbackLastRow() = 0;
70
71 virtual std::size_t ColumnsCount() const = 0;
72
73 void UpdateBinds(void* binds_array);
74
75 protected:
76 ~ExtractorBase();
77 // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
78 ResultBinder binder_;
79};
80
81template <typename Container, typename MapFrom, typename ExtractionTag>
82class TypedExtractor final : public ExtractorBase {
83 public:
84 TypedExtractor();
85
86 void Reserve(std::size_t size) final;
87
88 impl::bindings::OutputBindings& BindNextRow() final;
89
90 void CommitLastRow() final;
91
92 void RollbackLastRow() final;
93
94 std::size_t ColumnsCount() const final;
95
96 Container&& ExtractData();
97
98 private:
99 static constexpr std::size_t GetColumnsCount() {
100 if constexpr (std::is_same_v<ExtractionTag, RowTag>) {
101 return boost::pfr::tuple_size_v<MapFrom>;
102 } else if constexpr (std::is_same_v<ExtractionTag, FieldTag>) {
103 return 1;
104 } else {
105 static_assert(!sizeof(ExtractionTag), "should be unreachable");
106 }
107 }
108
109 static constexpr bool kIsMapped =
110 !std::is_same_v<typename Container::value_type, MapFrom>;
111 // TODO : add more std:: containers that allow us to emplace/emplace_back and
112 // then update the value (list, queue, deque etc)
113 static constexpr bool kCanBindInPlace =
114 std::is_same_v<Container, std::vector<MapFrom>>;
115
116 // If we are mapped we are out of luck - have to create a MapFrom instance
117 // first, and then insert it.
118 // If we can't bind in place the same applies.
119 // Otherwise, we can emplace_back and bind that new instance.
120 using InternalStorageType =
121 std::conditional_t<!kIsMapped && kCanBindInPlace,
122 InPlaceStorage<Container>,
123 ProxyingStorage<Container, MapFrom>>;
124 InternalStorageType storage_;
125};
126
127template <typename Container, typename MapFrom, typename ExtractionTag>
128TypedExtractor<Container, MapFrom, ExtractionTag>::TypedExtractor()
129 : ExtractorBase{GetColumnsCount()},
130 storage_{InternalStorageType{binder_}} {}
131
132template <typename Container, typename MapFrom, typename ExtractionTag>
133void TypedExtractor<Container, MapFrom, ExtractionTag>::Reserve(
134 std::size_t size) {
135 storage_.Reserve(size);
136}
137
138template <typename Container, typename MapFrom, typename ExtractionTag>
139impl::bindings::OutputBindings&
140TypedExtractor<Container, MapFrom, ExtractionTag>::BindNextRow() {
141 return storage_.template BindNextRow<ExtractionTag>();
142}
143
144template <typename Container, typename MapFrom, typename ExtractionTag>
145void TypedExtractor<Container, MapFrom, ExtractionTag>::CommitLastRow() {
146 storage_.CommitLastRow();
147}
148
149template <typename Container, typename MapFrom, typename ExtractionTag>
150void TypedExtractor<Container, MapFrom, ExtractionTag>::RollbackLastRow() {
151 storage_.RollbackLastRow();
152}
153
154template <typename Container, typename MapFrom, typename ExtractionTag>
155std::size_t TypedExtractor<Container, MapFrom, ExtractionTag>::ColumnsCount()
156 const {
157 return GetColumnsCount();
158}
159
160template <typename Container, typename MapFrom, typename ExtractionTag>
161Container&& TypedExtractor<Container, MapFrom, ExtractionTag>::ExtractData() {
162 return storage_.ExtractData();
163}
164
165template <typename Container>
166InPlaceStorage<Container>::InPlaceStorage(ResultBinder& binder)
167 : binder_{binder} {}
168
169template <typename Container>
170void InPlaceStorage<Container>::Reserve(std::size_t size) {
171 // +1 because we over-commit one row and then rollback it in default execution
172 // path
173 data_.reserve(size + 1);
174}
175
176template <typename Container>
177template <typename ExtractionTag>
178impl::bindings::OutputBindings& InPlaceStorage<Container>::BindNextRow() {
179 data_.emplace_back();
180 return binder_.BindTo(data_.back(), ExtractionTag{});
181}
182
183template <typename Container>
184void InPlaceStorage<Container>::CommitLastRow() {
185 // no-op, already committed
186}
187
188template <typename Container>
189void InPlaceStorage<Container>::RollbackLastRow() {
190 data_.pop_back();
191}
192
193template <typename Container>
194Container&& InPlaceStorage<Container>::ExtractData() {
195 return std::move(data_);
196}
197
198template <typename Container, typename MapFrom>
199ProxyingStorage<Container, MapFrom>::ProxyingStorage(ResultBinder& binder)
200 : binder_{binder} {}
201
202template <typename Container, typename MapFrom>
203void ProxyingStorage<Container, MapFrom>::Reserve(
204 [[maybe_unused]] std::size_t size) {
205 if constexpr (meta::kIsReservable<Container>) {
206 data_.reserve(size);
207 }
208}
209
210template <typename Container, typename MapFrom>
211template <typename ExtractionTag>
212impl::bindings::OutputBindings&
213ProxyingStorage<Container, MapFrom>::BindNextRow() {
214 return binder_.BindTo(row_, ExtractionTag{});
215}
216
217template <typename Container, typename MapFrom>
218void ProxyingStorage<Container, MapFrom>::CommitLastRow() {
219 if constexpr (kIsMapped) {
220 *meta::Inserter(data_) =
221 storages::mysql::convert::DoConvert<typename Container::value_type>(
222 std::move(row_));
223 } else {
224 *meta::Inserter(data_) = std::move(row_);
225 }
226}
227
228template <typename Container, typename MapFrom>
229void ProxyingStorage<Container, MapFrom>::RollbackLastRow() {
230 // no-op, because either this function or commit is called, not both
231}
232
233template <typename Container, typename MapFrom>
234Container&& ProxyingStorage<Container, MapFrom>::ExtractData() {
235 return std::move(data_);
236}
237
238} // namespace storages::mysql::impl::io
239
240USERVER_NAMESPACE_END