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 Cursor::empty.
140 /// @throws IgnoreResultsError if @ref Cursor::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 Cursor::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 /// @returns `true` if the cursor has no rows
162 bool empty() const;
163 /// @returns the number of rows in the cursor
164 std::size_t size() const;
165
166 CursorIterator begin();
167 std::default_sentinel_t end();
168
169private:
170 friend class Row;
171 friend class CursorIterator;
172
173 bool truncated_;
174 bool is_consumed_{false};
175 // Row should not be invalidated after moving Cursor, hence the indirection.
176 utils::UniqueRef<impl::ParseState> parse_state_;
177};
178
179class ExecuteResponse final {
180public:
181 /// @cond
182 explicit ExecuteResponse(NYdb::NQuery::TExecuteQueryResult&& query_result);
183 /// @endcond
184
185 ExecuteResponse(const ExecuteResponse&) = delete;
186 ExecuteResponse(ExecuteResponse&&) noexcept = default;
187 ExecuteResponse& operator=(const ExecuteResponse&) = delete;
188 ExecuteResponse& operator=(ExecuteResponse&&) = delete;
189
190 std::size_t GetCursorCount() const;
191 Cursor GetCursor(std::size_t index) const;
192 Cursor GetSingleCursor() const;
193
194 /// Query stats are only available if initially requested
195 const std::optional<NYdb::NTable::TQueryStats>& //
196 GetQueryStats() const noexcept;
197
198 /// Returns true if Execute used the server query cache
199 bool IsFromServerQueryCache() const noexcept;
200
201private:
202 void EnsureResultSetsNotEmpty() const;
203
204 std::optional<NYdb::NTable::TQueryStats> query_stats_;
205 std::vector<NYdb::TResultSet> result_sets_;
206};
207
208class ReadTableResults final {
209public:
210 /// @cond
211 explicit ReadTableResults(NYdb::NTable::TTablePartIterator iterator);
212 /// @endcond
213
214 std::optional<Cursor> GetNextResult();
215
216 ReadTableResults(const ReadTableResults&) = delete;
217 ReadTableResults(ReadTableResults&&) noexcept = default;
218 ReadTableResults& operator=(const ReadTableResults&) = delete;
219 ReadTableResults& operator=(ReadTableResults&&) = delete;
220
221private:
222 NYdb::NTable::TTablePartIterator iterator_;
223};
224
225class ScanQueryResults final {
226 using TScanQueryPartIterator = NYdb::NTable::TScanQueryPartIterator;
227
228public:
229 using TScanQueryPart = NYdb::NTable::TScanQueryPart;
230
231 /// @cond
232 explicit ScanQueryResults(TScanQueryPartIterator iterator);
233 /// @endcond
234
235 std::optional<TScanQueryPart> GetNextResult();
236
237 std::optional<Cursor> GetNextCursor();
238
239 ScanQueryResults(const ScanQueryResults&) = delete;
240 ScanQueryResults(ScanQueryResults&&) noexcept = default;
241 ScanQueryResults& operator=(const ScanQueryResults&) = delete;
242 ScanQueryResults& operator=(ScanQueryResults&&) = delete;
243
244private:
245 TScanQueryPartIterator iterator_;
246};
247
248template <typename T>
249T Row::As() && {
250 if (&typeid(T) != parse_state_.row_type_id) {
251 parse_state_.cpp_to_ydb_field_mapping = impl::StructRowParser<T>::MakeCppToYdbFieldMapping(parse_state_.parser);
252 parse_state_.row_type_id = &typeid(T);
253 }
254 return impl::StructRowParser<T>::ParseRow(parse_state_.parser, parse_state_.cpp_to_ydb_field_mapping);
255}
256
257template <typename T>
258T Row::Get(std::string_view column_name) {
259#ifndef NDEBUG
260 ConsumedColumnsCheck(parse_state_.parser.ColumnIndex(impl::ToString(column_name)));
261#endif
262 auto& column = GetColumn(column_name);
263 return Parse<T>(column, ParseContext{.column_name = column_name});
264}
265
266template <typename T>
267T Row::Get(std::size_t column_index) {
268#ifndef NDEBUG
269 ConsumedColumnsCheck(column_index);
270#endif
271 auto& column = GetColumn(column_index);
272 const auto column_name = std::to_string(column_index);
273 return Parse<T>(column, ParseContext{.column_name = column_name});
274}
275
276template <typename Container>
277Container Cursor::AsContainer() && {
278 using ValueType = typename Container::value_type;
279 Container c;
280 if constexpr (meta::kIsReservable<Container>) {
281 c.reserve(size());
282 }
283
284 auto inserter = meta::Inserter(c);
285 for (Row row : *this) {
286 *inserter = std::move(row).As<ValueType>();
287 ++inserter;
288 }
289
290 return c;
291}
292
293template <typename T>
294T Cursor::AsSingleRow() && {
295 return std::move(*this).GetSingleRow().As<T>();
296}
297
298template <typename T>
299std::optional<T> Cursor::AsOptionalSingleRow() && {
300 if (empty()) {
301 return std::nullopt;
302 }
303 return std::move(*this).AsSingleRow<T>();
304}
305
306} // namespace ydb
307
308USERVER_NAMESPACE_END