userver: userver/storages/mysql/statement_result_set.hpp Source File
Loading...
Searching...
No Matches
statement_result_set.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/mysql/statement_result_set.hpp
4
5#include <optional>
6#include <vector>
7
8#include <fmt/format.h>
9
10#include <userver/engine/deadline.hpp>
11#include <userver/tracing/scope_time.hpp>
12#include <userver/utils/fast_pimpl.hpp>
13
14#include <userver/storages/mysql/execution_result.hpp>
15#include <userver/storages/mysql/impl/io/extractor.hpp>
16#include <userver/storages/mysql/impl/tracing_tags.hpp>
17
18USERVER_NAMESPACE_BEGIN
19
20namespace storages::mysql {
21
22namespace impl {
23class StatementFetcher;
24}
25
26namespace infra {
27class ConnectionPtr;
28}
29
30template <typename DbType>
31class MappedStatementResultSet;
32
33/// @brief A wrapper for statement execution result.
34///
35/// This type can't be constructed in user code and is always retrieved from
36/// storages::mysql::Cluster or storages::mysql::Transaction methods.
37class StatementResultSet final {
38 public:
39 explicit StatementResultSet(impl::StatementFetcher&& fetcher,
40 tracing::Span&& span);
41 StatementResultSet(infra::ConnectionPtr&& connection,
42 impl::StatementFetcher&& fetcher, tracing::Span&& span);
43 ~StatementResultSet();
44
45 StatementResultSet(const StatementResultSet& other) = delete;
46 StatementResultSet(StatementResultSet&& other) noexcept;
47
48 // clang-format off
49 /// @brief Parse statement result set as std::vector<T>.
50 /// `T` is expected to be an aggregate of supported types.
51 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for better understanding of
52 /// `T` requirements.
53 ///
54 /// UINVARIANTs on columns count mismatch or types mismatch.
55 ///
56 /// @snippet storages/tests/unittests/statement_result_set_mysqltest.cpp uMySQL usage sample - StatementResultSet AsVector
57 // clang-format on
58 template <typename T>
59 std::vector<T> AsVector() &&;
60
61 // clang-format off
62 /// @brief Parse statement result set as std::vector<T>.
63 /// Result set is expected to have a single column, `T` is expected to be one
64 /// of supported types.
65 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for supported typed.
66 ///
67 /// UINVARIANTs on columns count not being equal to 1 or type mismatch.
68 ///
69 /// @snippet storages/tests/unittests/statement_result_set_mysqltest.cpp uMySQL usage sample - StatementResultSet AsVectorFieldTag
70 // clang-format on
71 template <typename T>
73
74 // clang-format off
75 /// @brief Parse statement result set as Container<T>.
76 /// `T` is expected to be an aggregate of supported types, `Container` is
77 /// expected to meet std::Container requirements.
78 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for better understanding of
79 /// `Container::value_type` requirements.
80 ///
81 /// UINVARIANTs on columns count mismatch or types mismatch.
82 ///
83 /// @snippet storages/tests/unittests/statement_result_set_mysqltest.cpp uMySQL usage sample - StatementResultSet AsContainer
84 // clang-format on
85 template <typename Container>
86 Container AsContainer() &&;
87
88 // clang-format off
89 /// @brief Parse statement result as Container<T>.
90 /// Result set is expected to have a single column, `T` is expected to be one
91 /// of supported types,
92 /// `Container` is expected to meed std::Container requirements.
93 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for supported types.
94 ///
95 /// UINVARIANTs on columns count not being equal to 1 or type mismatch.
96 ///
97 /// @snippet storages/tests/unittests/statement_result_set_mysqltest.cpp uMySQL usage sample - StatementResultSet AsContainerFieldTag
98 // clang-format on
99 template <typename Container>
100 Container AsContainer(FieldTag) &&;
101
102 // clang-format off
103 /// @brief Parse statement result as T.
104 /// Result set is expected to have a single row, `T` is expected to be an
105 /// aggregate of supported types.
106 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for better understanding of
107 /// `T` requirements.
108 ///
109 /// UINVARIANTs on columns count mismatch or types mismatch.
110 /// throws if result set is empty or contains more than one row.
111 ///
112 /// @snippet storages/tests/unittests/statement_result_set_mysqltest.cpp uMySQL usage sample - StatementResultSet AsSingleRow
113 // clang-format on
114 template <typename T>
115 T AsSingleRow() &&;
116
117 // clang-format off
118 /// @brief Parse statement result as T.
119 /// Result set is expected to have a single row and a single column,
120 /// `T` is expected to be one of supported types.
121 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for supported types.
122 ///
123 /// UINVARIANTs on columns count not being equal to 1 or type mismatch.
124 /// throws if result set is empty of contains more than one row.
125 ///
126 /// @snippet storages/tests/unittests/statement_result_set_mysqltest.cpp uMySQL usage sample - StatementResultSet AsSingleField
127 // clang-format on
128 template <typename T>
129 T AsSingleField() &&;
130
131 // clang-format off
132 /// @brief Parse statement result as std::optional<T>.
133 /// Result set is expected to have not more than one row,
134 /// `T` is expected to be an aggregate of supported types.
135 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for better understanding of
136 /// `T` requirements.
137 ///
138 /// UINVARIANTs on columns count mismatch or types mismatch.
139 /// throws if result set contains more than one row.
140 ///
141 /// @snippet storages/tests/unittests/statement_result_set_mysqltest.cpp uMySQL usage sample - StatementResultSet AsOptionalSingleRow
142 // clang-format on
143 template <typename T>
144 std::optional<T> AsOptionalSingleRow() &&;
145
146 // clang-format off
147 /// @brief Parse statement result as T.
148 /// Result set is expected to have not more than one row,
149 /// `T` is expected to be one of supported types.
150 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for supported types.
151 ///
152 /// UINVARIANTs on columns count not being equal to 1 or type mismatch.
153 /// throws if result set contains more than one row.
154 ///
155 /// @snippet storages/tests/unittests/statement_result_set_mysqltest.cpp uMySQL usage sample - StatementResultSet AsOptionalSingleField
156 // clang-format on
157 template <typename T>
158 std::optional<T> AsOptionalSingleField() &&;
159
160 // clang-format off
161 /// @brief Converts to an interface for on-the-flight mapping
162 /// statement result set from `DbType`.
163 /// `DbType` is expected to be an aggregate of supported types.
164 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for better understanding of
165 /// `DbType` requirements.
166 ///
167 /// @snippet storages/tests/unittests/statement_result_set_mysqltest.cpp uMySQL usage sample - StatementResultSet MapFrom
168 // clang-format on
169 template <typename DbType>
170 MappedStatementResultSet<DbType> MapFrom() &&;
171
172 /// @brief Get statement execution metadata.
173 ExecutionResult AsExecutionResult() &&;
174
175 private:
176 template <typename T>
177 friend class CursorResultSet;
178
179 template <typename DbType>
180 friend class MappedStatementResultSet;
181
182 template <typename Container, typename MapFrom, typename ExtractionTag>
183 Container DoAsContainerMapped() &&;
184
185 template <typename T, typename ExtractionTag>
186 std::optional<T> DoAsOptionalSingleRow() &&;
187
188 bool FetchResult(impl::io::ExtractorBase& extractor);
189
190 struct Impl;
191 utils::FastPimpl<Impl, 72, 8> impl_;
192};
193
194/// @brief An interface for on-the-flight mapping statement result set from
195/// DbType into whatever type you provide without additional allocations.
196///
197/// `DbType` is expected to be either an aggregate of supported types or a
198/// supported type itself for methods taking `FieldTag`; DbType is expected
199/// to match DB representation - same amount of columns (1 for `FieldTag`
200/// overload) and matching types.
201///
202/// You are expected to provide a converter function
203/// `T Convert(DbType&&, storages::mysql::convert:To<T>)` in either
204/// T's namespace or `storages::mysql::convert` namespace
205template <typename DbType>
206class MappedStatementResultSet final {
207 public:
208 explicit MappedStatementResultSet(StatementResultSet&& result_set);
209 ~MappedStatementResultSet();
210
211 /// @brief Parse statement result set as std::vector<T> using provided
212 /// converter function.
213 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for better
214 /// understanding of `T` requirements.
215 ///
216 /// UINVARIANTs on columns count mismatch or types mismatch for `DbType`.
217 template <typename T>
218 std::vector<T> AsVector() &&;
219
220 /// @brief Parse statement result set as std::vector<T> using provided
221 /// converter function.
222 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for supported
223 /// types.
224 ///
225 /// UINVARIANTs on columns count not being 1 or types mismatch for DbType.
226 template <typename T>
228
229 /// @brief Parse statement result set as Container<T> using provided
230 /// converter function.
231 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for better
232 /// understanding of `Container::value_type` requirements.
233 ///
234 /// UINVARIANTs on columns count mismatch or types mismatch for `DbType`.
235 template <typename Container>
236 Container AsContainer() &&;
237
238 /// @brief Parse statement result set as Container<T> using provided
239 /// converter function.
240 /// See @ref scripts/docs/en/userver/mysql/supported_types.md for supported
241 /// types.
242 ///
243 /// UINVARIANTs on columns count not being 1 or types mismatch for DbType.
244 template <typename Container>
245 Container AsContainer(FieldTag) &&;
246
247 template <typename T>
248 T AsSingleRow() && {
249 static_assert(!sizeof(T),
250 "Not implemented, just use StatementResultSet version and "
251 "convert yourself.");
252 }
253
254 template <typename T>
255 std::optional<T> AsOptionalSingleRow() && {
256 static_assert(!sizeof(T),
257 "Not implemented, just use StatementResultSet version and "
258 "convert yourself.");
259 }
260
261 private:
262 StatementResultSet result_set_;
263};
264
265template <typename T>
266std::vector<T> StatementResultSet::AsVector() && {
267 return std::move(*this).AsContainer<std::vector<T>>();
268}
269
270template <typename T>
271std::vector<T> StatementResultSet::AsVector(FieldTag) && {
272 return std::move(*this).AsContainer<std::vector<T>>(kFieldTag);
273}
274
275template <typename Container>
276Container StatementResultSet::AsContainer() && {
277 static_assert(meta::kIsRange<Container>,
278 "The type isn't actually a container");
279 using Row = typename Container::value_type;
280
281 return std::move(*this).DoAsContainerMapped<Container, Row, RowTag>();
282}
283
284template <typename Container>
285Container StatementResultSet::AsContainer(FieldTag) && {
286 static_assert(meta::kIsRange<Container>,
287 "The type isn't actually a container");
288 using Row = typename Container::value_type;
289
290 return std::move(*this).DoAsContainerMapped<Container, Row, FieldTag>();
291}
292
293template <typename T>
294T StatementResultSet::AsSingleRow() && {
295 auto optional_data = std::move(*this).AsOptionalSingleRow<T>();
296
297 if (!optional_data.has_value()) {
298 throw std::runtime_error{"Result set is empty"};
299 }
300
301 return std::move(*optional_data);
302}
303
304template <typename T>
305T StatementResultSet::AsSingleField() && {
306 auto optional_data = std::move(*this).AsOptionalSingleField<T>();
307
308 if (!optional_data.has_value()) {
309 throw std::runtime_error{"Result set is empty"};
310 }
311
312 return std::move(*optional_data);
313}
314
315template <typename T>
316std::optional<T> StatementResultSet::AsOptionalSingleRow() && {
317 return std::move(*this).DoAsOptionalSingleRow<T, RowTag>();
318}
319
320template <typename T>
321std::optional<T> StatementResultSet::AsOptionalSingleField() && {
322 return std::move(*this).DoAsOptionalSingleRow<T, FieldTag>();
323}
324
325template <typename Container, typename MapFrom, typename ExtractionTag>
326Container StatementResultSet::DoAsContainerMapped() && {
327 static_assert(meta::kIsRange<Container>,
328 "The type isn't actually a container");
329 using Extractor = impl::io::TypedExtractor<Container, MapFrom, ExtractionTag>;
330
331 Extractor extractor{};
332
333 tracing::ScopeTime fetch{impl::tracing::kFetchScope};
334 std::move(*this).FetchResult(extractor);
335 fetch.Reset();
336
337 return Container{extractor.ExtractData()};
338}
339
340template <typename T, typename ExtractionTag>
341std::optional<T> StatementResultSet::DoAsOptionalSingleRow() && {
342 auto rows = [this] {
343 if constexpr (std::is_same_v<RowTag, ExtractionTag>) {
344 return std::move(*this).AsVector<T>();
345 } else {
346 return std::move(*this).AsVector<T>(kFieldTag);
347 }
348 }();
349
350 if (rows.empty()) {
351 return std::nullopt;
352 }
353
354 if (rows.size() > 1) {
355 throw std::runtime_error{fmt::format(
356 "There is more than one row in result set ({} rows)", rows.size())};
357 }
358
359 return {std::move(rows.front())};
360}
361
362template <typename DbType>
363MappedStatementResultSet<DbType>::MappedStatementResultSet(
364 StatementResultSet&& result_set)
365 : result_set_{std::move(result_set)} {}
366
367template <typename DbType>
368MappedStatementResultSet<DbType>::~MappedStatementResultSet() = default;
369
370template <typename DbType>
371template <typename T>
372std::vector<T> MappedStatementResultSet<DbType>::AsVector() && {
373 return std::move(*this).template AsContainer<std::vector<T>>();
374}
375
376template <typename DbType>
377template <typename T>
378std::vector<T> MappedStatementResultSet<DbType>::AsVector(FieldTag) && {
379 return std::move(*this).template AsContainer<std::vector<T>>(kFieldTag);
380}
381
382template <typename DbType>
383template <typename Container>
384Container MappedStatementResultSet<DbType>::AsContainer() && {
385 return std::move(result_set_)
386 .DoAsContainerMapped<Container, DbType, RowTag>();
387}
388
389template <typename DbType>
390template <typename Container>
391Container MappedStatementResultSet<DbType>::AsContainer(FieldTag) && {
392 return std::move(result_set_)
393 .DoAsContainerMapped<Container, DbType, FieldTag>();
394}
395
396template <typename DbType>
397MappedStatementResultSet<DbType> StatementResultSet::MapFrom() && {
398 return MappedStatementResultSet<DbType>{std::move(*this)};
399}
400
401} // namespace storages::mysql
402
403USERVER_NAMESPACE_END