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