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