userver: userver/ydb/response.hpp Source File
Loading...
Searching...
No Matches
response.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/ydb/response.hpp
4/// @brief YDB query result rows, cursors and execute responses
5
6#include <ydb-cpp-sdk/client/query/client.h>
7#include <ydb-cpp-sdk/client/result/result.h>
8#include <ydb-cpp-sdk/client/table/table.h>
9
10#include <cstddef>
11#include <iterator>
12#include <memory>
13#include <optional>
14#include <typeinfo>
15
16#include <userver/utils/meta.hpp>
17#include <userver/utils/not_null.hpp>
18#include <userver/ydb/impl/cast.hpp>
19#include <userver/ydb/io/insert_row.hpp>
20#include <userver/ydb/io/list.hpp>
21#include <userver/ydb/io/primitives.hpp>
22#include <userver/ydb/io/traits.hpp>
23#include <userver/ydb/types.hpp>
24
25USERVER_NAMESPACE_BEGIN
26
27namespace ydb {
28
29namespace impl {
30
31template <typename T>
32struct StructRowParser;
33
34struct ParseState final {
35 explicit ParseState(const NYdb::TResultSet& result_set);
36
37 NYdb::TResultSetParser parser;
38 const std::type_info* row_type_id{nullptr};
39 std::unique_ptr<std::size_t[]> cpp_to_ydb_field_mapping{};
40};
41
42} // namespace impl
43
44using ValueType = NYdb::EPrimitiveType;
45
46class Cursor;
47
48class Row final {
49public:
50 /// @cond
51 // For internal use only.
52 explicit Row(impl::ParseState& parse_state);
53 /// @endcond
54
55 Row(const Row&) = delete;
56 Row(Row&&) noexcept = default;
57 Row& operator=(const Row&) = delete;
58 Row& operator=(Row&&) = delete;
59
60 /// @brief Parses the whole row to `T`, which must be a struct type.
61 /// ydb::kStructMemberNames must be specialized for `T`.
62 ///
63 /// @throws ydb::ColumnParseError on parsing error
64 /// @throws ydb::ParseError on extra fields on C++ side
65 /// @throws ydb::ParseError on extra fields on YDB side
66 template <typename T>
67 T As() &&;
68
69 /// @brief Parses the specified column to `T`.
70 /// `Get` can only be called once for each column.
71 /// @throws ydb::BaseError on parsing error
72 template <typename T>
73 T Get(std::string_view column_name);
74
75 /// @brief Parses the specified column to `T`.
76 /// `Get` can only be called once for each column.
77 /// @throws ydb::BaseError on parsing error
78 template <typename T>
79 T Get(std::size_t column_index);
80
81private:
82 NYdb::TValueParser& GetColumn(std::size_t index);
83 NYdb::TValueParser& GetColumn(std::string_view name);
84
85 void ConsumedColumnsCheck(std::size_t column_index);
86
87 impl::ParseState& parse_state_;
88 std::vector<bool> consumed_columns_;
89};
90
91class CursorIterator final {
92public:
93 using difference_type = std::ptrdiff_t;
94 using value_type = Row;
95 using reference = Row;
96 using iterator_category = std::input_iterator_tag;
97
98 CursorIterator() = default;
99
100 CursorIterator(const CursorIterator&) = delete;
101 CursorIterator(CursorIterator&&) noexcept = default;
102 CursorIterator& operator=(const CursorIterator&) = delete;
103 CursorIterator& operator=(CursorIterator&&) = default;
104
105 Row operator*() const;
106
107 CursorIterator& operator++();
108
109 void operator++(int);
110
111 bool operator==(const std::default_sentinel_t& other) const noexcept;
112
113private:
114 friend class Cursor;
115
116 explicit CursorIterator(Cursor& cursor);
117
118 impl::ParseState* parse_state_{nullptr};
119};
120
121class Cursor final {
122public:
123 /// @cond
124 explicit Cursor(const NYdb::TResultSet& result_set);
125 /// @endcond
126
127 Cursor(const Cursor&) = delete;
128 Cursor(Cursor&&) noexcept = default;
129 Cursor& operator=(const Cursor&) = delete;
130 Cursor& operator=(Cursor&&) noexcept = default;
131
132 size_t ColumnsCount() const;
133 size_t RowsCount() const;
134 /// @throws EmptyResponseError if GetFirstRow() or begin() called before or
135 /// cursor is empty
137
138 /// @brief Extract first row
139 /// @throws EmptyResponseError if @ref empty().
140 /// @throws IgnoreResultsError if @ref size() > 1.
141 Row GetSingleRow() &&;
142
143 /// @brief Extract data into a container. Each row is parsed using @ref Row::As.
144 template <typename Container>
145 Container AsContainer() &&;
146
147 /// @brief Extract first row into user type using @ref Row::As.
148 /// @throws EmptyResponseError if @ref empty().
149 template <typename T>
150 T AsSingleRow() &&;
151
152 /// @brief Extract first row into user type using @ref Row::As.
153 /// @returns A single row result set if non empty result was returned, empty `std::optional` otherwise
154 template <typename T>
155 std::optional<T> AsOptionalSingleRow() &&;
156
157 /// Returns true if response has been truncated to the database limit
158 /// (currently 1000 rows)
159 bool IsTruncated() const;
160
161 bool empty() const;
162 std::size_t size() const;
163
164 CursorIterator begin();
165 std::default_sentinel_t end();
166
167private:
168 friend class Row;
169 friend class CursorIterator;
170
171 bool truncated_;
172 bool is_consumed_{false};
173 // Row should not be invalidated after moving Cursor, hence the indirection.
174 utils::UniqueRef<impl::ParseState> parse_state_;
175};
176
177class ExecuteResponse final {
178public:
179 /// @cond
180 explicit ExecuteResponse(std::variant<NYdb::NQuery::TExecuteQueryResult, NYdb::NTable::TDataQueryResult>&&
181 query_result);
182 /// @endcond
183
184 ExecuteResponse(const ExecuteResponse&) = delete;
185 ExecuteResponse(ExecuteResponse&&) noexcept = default;
186 ExecuteResponse& operator=(const ExecuteResponse&) = delete;
187 ExecuteResponse& operator=(ExecuteResponse&&) = delete;
188
189 std::size_t GetCursorCount() const;
190 Cursor GetCursor(std::size_t index) const;
191 Cursor GetSingleCursor() const;
192
193 /// Query stats are only available if initially requested
194 const std::optional<NYdb::NTable::TQueryStats>& //
195 GetQueryStats() const noexcept;
196
197 /// Returns true if Execute used the server query cache
198 bool IsFromServerQueryCache() const noexcept;
199
200private:
201 void EnsureResultSetsNotEmpty() const;
202
203 std::optional<NYdb::NTable::TQueryStats> query_stats_;
204 std::vector<NYdb::TResultSet> result_sets_;
205};
206
207class ReadTableResults final {
208public:
209 /// @cond
210 explicit ReadTableResults(NYdb::NTable::TTablePartIterator iterator);
211 /// @endcond
212
213 std::optional<Cursor> GetNextResult();
214
215 ReadTableResults(const ReadTableResults&) = delete;
216 ReadTableResults(ReadTableResults&&) noexcept = default;
217 ReadTableResults& operator=(const ReadTableResults&) = delete;
218 ReadTableResults& operator=(ReadTableResults&&) = delete;
219
220private:
221 NYdb::NTable::TTablePartIterator iterator_;
222};
223
224class ScanQueryResults final {
225 using TScanQueryPartIterator = NYdb::NTable::TScanQueryPartIterator;
226
227public:
228 using TScanQueryPart = NYdb::NTable::TScanQueryPart;
229
230 /// @cond
231 explicit ScanQueryResults(TScanQueryPartIterator iterator);
232 /// @endcond
233
234 std::optional<TScanQueryPart> GetNextResult();
235
236 std::optional<Cursor> GetNextCursor();
237
238 ScanQueryResults(const ScanQueryResults&) = delete;
239 ScanQueryResults(ScanQueryResults&&) noexcept = default;
240 ScanQueryResults& operator=(const ScanQueryResults&) = delete;
241 ScanQueryResults& operator=(ScanQueryResults&&) = delete;
242
243private:
244 TScanQueryPartIterator iterator_;
245};
246
247template <typename T>
248T Row::As() && {
249 if (&typeid(T) != parse_state_.row_type_id) {
250 parse_state_.cpp_to_ydb_field_mapping = impl::StructRowParser<T>::MakeCppToYdbFieldMapping(parse_state_.parser);
251 parse_state_.row_type_id = &typeid(T);
252 }
253 return impl::StructRowParser<T>::ParseRow(parse_state_.parser, parse_state_.cpp_to_ydb_field_mapping);
254}
255
256template <typename T>
257T Row::Get(std::string_view column_name) {
258#ifndef NDEBUG
259 ConsumedColumnsCheck(parse_state_.parser.ColumnIndex(impl::ToString(column_name)));
260#endif
261 auto& column = GetColumn(column_name);
262 return Parse<T>(column, ParseContext{.column_name = column_name});
263}
264
265template <typename T>
266T Row::Get(std::size_t column_index) {
267#ifndef NDEBUG
268 ConsumedColumnsCheck(column_index);
269#endif
270 auto& column = GetColumn(column_index);
271 const auto column_name = std::to_string(column_index);
272 return Parse<T>(column, ParseContext{.column_name = column_name});
273}
274
275template <typename Container>
276Container Cursor::AsContainer() && {
277 using ValueType = typename Container::value_type;
278 Container c;
279 if constexpr (meta::kIsReservable<Container>) {
280 c.reserve(size());
281 }
282
283 auto inserter = meta::Inserter(c);
284 for (Row row : *this) {
285 *inserter = std::move(row).As<ValueType>();
286 ++inserter;
287 }
288
289 return c;
290}
291
292template <typename T>
293T Cursor::AsSingleRow() && {
294 return std::move(*this).GetSingleRow().As<T>();
295}
296
297template <typename T>
298std::optional<T> Cursor::AsOptionalSingleRow() && {
299 if (empty()) {
300 return std::nullopt;
301 }
302 return std::move(*this).AsSingleRow<T>();
303}
304
305} // namespace ydb
306
307USERVER_NAMESPACE_END