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