userver: userver/storages/mysql/statement_result_set.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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