userver: userver/storages/postgres/row.hpp Source File
Loading...
Searching...
No Matches
row.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file
4/// @brief Result row accessors
5
6#include <initializer_list>
7#include <optional>
8#include <tuple>
9#include <type_traits>
10#include <utility>
11#include <variant>
12
13#include <userver/storages/postgres/field.hpp>
14#include <userver/storages/postgres/io/supported_types.hpp>
15#include <userver/utils/zstring_view.hpp>
16
17#include <userver/logging/log.hpp>
18
19USERVER_NAMESPACE_BEGIN
20
21namespace storages::postgres {
22
23class ResultSet;
24template <typename T, typename ExtractionTag>
25class TypedResultSet;
26
27/// @brief A wrapper for PGresult to access field descriptions.
29public:
30 RowDescription(detail::ResultWrapperPtr res)
31 : res_{std::move(res)}
32 {}
33
34 /// Check that all fields can be read in binary format
35 /// @throw NoBinaryParser if any of the fields doesn't have a binary parser
36 void CheckBinaryFormat(const UserTypes& types) const;
37
38 // TODO interface for iterating field descriptions
39private:
40 detail::ResultWrapperPtr res_;
41};
42
43/// Data row in a result set
44/// This class is a mere accessor to underlying result set data buffer,
45/// must not be used outside of result set life scope.
46///
47/// Mimics field container
48class Row {
49public:
50 //@{
51 /** @name Field container concept */
52 using size_type = std::size_t;
53 using const_iterator = ConstFieldIterator;
54 using const_reverse_iterator = ReverseConstFieldIterator;
55
56 using value_type = Field;
57 using reference = Field;
58 using pointer = const_iterator;
59 //@}
60
61 size_type RowIndex() const { return row_index_; }
62
63 RowDescription GetDescription() const { return {res_}; }
64 //@{
65 /** @name Field container interface */
66 /// Number of fields
67 size_type Size() const;
68
69 //@{
70 /** @name Forward iteration */
71 const_iterator cbegin() const;
72 const_iterator begin() const { return cbegin(); }
73 const_iterator cend() const;
74 const_iterator end() const { return cend(); }
75 //@}
76 //@{
77 /** @name Reverse iteration */
78 const_reverse_iterator crbegin() const;
79 const_reverse_iterator rbegin() const { return crbegin(); }
80 const_reverse_iterator crend() const;
81 const_reverse_iterator rend() const { return crend(); }
82 //@}
83
84 /// @brief Field access by index
85 /// @throws FieldIndexOutOfBounds if index is out of bounds
86 reference operator[](size_type index) const;
87 /// @brief Field access field by name
88 /// @throws FieldNameDoesntExist if the result set doesn't contain
89 /// such a field
90 reference operator[](USERVER_NAMESPACE::utils::zstring_view name) const;
91 //@}
92
93 //@{
94 /** @name Access to row's data */
95 /// Read the contents of the row to a user's row type or read the first
96 /// column into the value.
97 ///
98 /// If the user tries to read the first column into a variable, it must be the
99 /// only column in the result set. If the result set contains more than one
100 /// column, the function will throw NonSingleColumnResultSet. If the result
101 /// set is OK to contain more than one columns, the first column value should
102 /// be accessed via `row[0].To/As`.
103 ///
104 /// If the type is a 'row' type, the function will read the fields of the row
105 /// into the type's data members.
106 ///
107 /// If the type can be treated as both a row type and a composite type (the
108 /// type is mapped to a PostgreSQL type), the function will treat the type
109 /// as a type for the first (and the only) column.
110 ///
111 /// To read the all fields of the row as a row type, the To(T&&, RowTag)
112 /// should be used.
113 template <typename T>
114 void To(T&& val) const;
115
116 /// Function to disambiguate reading the row to a user's row type (values
117 /// of the row initialize user's type data members)
118 template <typename T>
119 void To(T&& val, RowTag) const;
120
121 /// Function to disambiguate reading the first column to a user's composite
122 /// type (PostgreSQL composite type in the row initializes user's type).
123 /// The same as calling To(T&& val) for a T mapped to a PostgreSQL type.
124 template <typename T>
125 void To(T&& val, FieldTag) const;
126
127 /// Read fields into variables in order of their appearance in the row
128 template <typename... T>
129 void To(T&&... val) const;
130
131 /// @brief Parse values from the row and return the result.
132 ///
133 /// If there are more than one type arguments to the function, it will
134 /// return a tuple of those types.
135 ///
136 /// If there is a single type argument to the function, it will read the first
137 /// and the only column of the row or the whole row to the row type (depending
138 /// on C++ to PosgreSQL mapping presence) and return plain value of this type.
139 ///
140 /// @see To(T&&)
141 template <typename T, typename... Y>
142 auto As() const;
143
144 /// @brief Returns T initialized with values of the row.
145 /// @snippet storages/postgres/tests/typed_rows_pgtest.cpp RowTagSippet
146 template <typename T>
147 T As(RowTag) const {
148 T val{};
149 To(val, kRowTag);
150 return val;
151 }
152
153 /// @brief Returns T initialized with a single column value of the row.
154 /// @snippet storages/postgres/tests/composite_types_pgtest.cpp FieldTagSippet
155 template <typename T>
156 T As(FieldTag) const {
157 T val{};
158 To(val, kFieldTag);
159 return val;
160 }
161
162 /// Read fields into variables in order of their names in the first argument
163 template <typename... T>
164 void To(const std::initializer_list<USERVER_NAMESPACE::utils::zstring_view>& names, T&&... val) const;
165 template <typename... T>
166 std::tuple<T...> As(const std::initializer_list<USERVER_NAMESPACE::utils::zstring_view>& names) const;
167
168 /// Read fields into variables in order of their indexes in the first
169 /// argument
170 template <typename... T>
171 void To(const std::initializer_list<size_type>& indexes, T&&... val) const;
172 template <typename... T>
173 std::tuple<T...> As(const std::initializer_list<size_type>& indexes) const;
174 //@}
175
176 size_type IndexOfName(USERVER_NAMESPACE::utils::zstring_view) const;
177
178 FieldView GetFieldView(size_type index) const;
179
180protected:
181 friend class ResultSet;
182
183 template <typename T, typename Tag>
184 friend class TypedResultSet;
185
186 Row() = default;
187
188 Row(detail::ResultWrapperPtr res, size_type row)
189 : res_{std::move(res)},
190 row_index_{row}
191 {}
192
193 //@{
194 /** @name Iteration support */
195 bool IsValid() const;
196 int Compare(const Row& rhs) const;
197 std::ptrdiff_t Distance(const Row& rhs) const;
198 Row& Advance(std::ptrdiff_t);
199 //@}
200private:
201 detail::ResultWrapperPtr res_;
202 size_type row_index_{0};
203};
204
205/// @name Iterator over rows in a result set
206class ConstRowIterator : public detail::ConstDataIterator<ConstRowIterator, Row, detail::IteratorDirection::kForward> {
207public:
208 ConstRowIterator() = default;
209
210private:
211 friend class ResultSet;
212
213 ConstRowIterator(detail::ResultWrapperPtr res, size_type row)
214 : ConstDataIterator(std::move(res), row)
215 {}
216};
217
218/// @name Reverse iterator over rows in a result set
220 : public detail::ConstDataIterator<ReverseConstRowIterator, Row, detail::IteratorDirection::kReverse> {
221public:
222 ReverseConstRowIterator() = default;
223
224private:
225 friend class ResultSet;
226
227 ReverseConstRowIterator(detail::ResultWrapperPtr res, size_type row)
228 : ConstDataIterator(std::move(res), row)
229 {}
230};
231
232namespace detail {
233
234template <typename T>
235struct IsOptionalFromOptional : std::false_type {};
236
237template <typename T>
238struct IsOptionalFromOptional<std::optional<std::optional<T>>> : std::true_type {};
239
240template <typename T>
241struct IsOneVariant : std::false_type {};
242
243template <typename T>
244struct IsOneVariant<std::variant<T>> : std::true_type {};
245
246template <typename... Args>
247constexpr void AssertSaneTypeToDeserialize() {
248 static_assert(
249 !(IsOptionalFromOptional<std::remove_const_t<std::remove_reference_t<Args>>>::value || ...),
250 "Attempt to get an optional<optional<T>> was detected. Such "
251 "optional-from-optional types are very error prone, obfuscate code and "
252 "are ambiguous to deserialize. Change the type to just optional<T>"
253 );
254 static_assert(
255 !(IsOneVariant<std::remove_const_t<std::remove_reference_t<Args>>>::value || ...),
256 "Attempt to get an variant<T> was detected. Such variant from one type "
257 "obfuscates code. Change the type to just T"
258 );
259}
260
261//@{
262/** @name Sequental field extraction */
263template <typename IndexTuple, typename... T>
264struct RowDataExtractorBase;
265
266template <std::size_t... Indexes, typename... T>
267struct RowDataExtractorBase<std::index_sequence<Indexes...>, T...> {
268 static void ExtractValues(const Row& row, T&&... val) {
269 static_assert(sizeof...(Indexes) == sizeof...(T));
270
271 std::size_t field_index = 0;
272 const auto perform = [&](auto&& arg) { row.GetFieldView(field_index++).To(std::forward<decltype(arg)>(arg)); };
273 (perform(std::forward<T>(val)), ...);
274 }
275 static void ExtractTuple(const Row& row, std::tuple<T...>& val) {
276 static_assert(sizeof...(Indexes) == sizeof...(T));
277
278 std::size_t field_index = 0;
279 const auto perform = [&](auto& arg) { row.GetFieldView(field_index++).To(arg); };
280 (perform(std::get<Indexes>(val)), ...);
281 }
282 static void ExtractTuple(const Row& row, std::tuple<T...>&& val) {
283 static_assert(sizeof...(Indexes) == sizeof...(T));
284
285 std::size_t field_index = 0;
286 const auto perform = [&](auto& arg) { row.GetFieldView(field_index++).To(arg); };
287 (perform(std::get<Indexes>(val)), ...);
288 }
289
290 static void ExtractValues(
291 const Row& row,
292 const std::initializer_list<USERVER_NAMESPACE::utils::zstring_view>& names,
293 T&&... val
294 ) {
295 (row[*(names.begin() + Indexes)].To(std::forward<T>(val)), ...);
296 }
297 static void ExtractTuple(
298 const Row& row,
299 const std::initializer_list<USERVER_NAMESPACE::utils::zstring_view>& names,
300 std::tuple<T...>& val
301 ) {
302 std::tuple<T...> tmp{row[*(names.begin() + Indexes)].template As<T>()...};
303 tmp.swap(val);
304 }
305
306 static void ExtractValues(const Row& row, const std::initializer_list<std::size_t>& indexes, T&&... val) {
307 (row[*(indexes.begin() + Indexes)].To(std::forward<T>(val)), ...);
308 }
309 static void ExtractTuple(const Row& row, const std::initializer_list<std::size_t>& indexes, std::tuple<T...>& val) {
310 std::tuple<T...> tmp{row[*(indexes.begin() + Indexes)].template As<T>()...};
311 tmp.swap(val);
312 }
313};
314
315template <typename... T>
316struct RowDataExtractor : RowDataExtractorBase<std::index_sequence_for<T...>, T...> {};
317
318template <typename T>
319struct TupleDataExtractor;
320template <typename... T>
321struct TupleDataExtractor<std::tuple<T...>> : RowDataExtractorBase<std::index_sequence_for<T...>, T...> {};
322//@}
323
324template <typename RowType>
325constexpr void AssertRowTypeIsMappedToPgOrIsCompositeType() {
326 // composite types can be parsed without an explicit mapping
327 static_assert(
328 io::traits::kIsMappedToPg<RowType> || io::traits::kIsCompositeType<RowType>,
329 "Row type must be mapped to pg type(CppToUserPg) or one of the "
330 "following: "
331 "1. primitive type. "
332 "2. std::tuple. "
333 "3. Aggregation type. See std::aggregation. "
334 "4. Has a Introspect method that makes the std::tuple from your "
335 "class/struct. "
336 "For more info see `uPg: Typed PostgreSQL results` chapter in docs."
337 );
338}
339
340} // namespace detail
341
342template <typename T>
343void Row::To(T&& val) const {
344 To(std::forward<T>(val), kFieldTag);
345}
346
347template <typename T>
348void Row::To(T&& val, RowTag) const {
349 detail::AssertSaneTypeToDeserialize<T>();
350 // Convert the val into a writable tuple and extract the data
351 using ValueType = std::decay_t<T>;
352 io::traits::AssertIsValidRowType<ValueType>();
353 using RowType = io::RowType<ValueType>;
354 using TupleType = typename RowType::TupleType;
355 constexpr auto tuple_size = RowType::size;
356 if (tuple_size > Size()) {
357 throw InvalidTupleSizeRequested(Size(), tuple_size);
358 } else if (tuple_size < Size()) {
360 << "Row size is greater that the number of data members in "
361 "C++ user datatype "
362 << compiler::GetTypeName<T>();
363 }
364
365 detail::TupleDataExtractor<TupleType>::ExtractTuple(*this, RowType::GetTuple(std::forward<T>(val)));
366}
367
368template <typename T>
369void Row::To(T&& val, FieldTag) const {
370 detail::AssertSaneTypeToDeserialize<T>();
371 using ValueType = std::decay_t<T>;
372 detail::AssertRowTypeIsMappedToPgOrIsCompositeType<ValueType>();
373 // Read the first field into the type
374 if (Size() < 1) {
376 }
377 if (Size() > 1) {
378 throw NonSingleColumnResultSet{Size(), compiler::GetTypeName<T>(), "As"};
379 }
380 (*this)[0].To(std::forward<T>(val));
381}
382
383template <typename... T>
384void Row::To(T&&... val) const {
385 detail::AssertSaneTypeToDeserialize<T...>();
386 if (sizeof...(T) > Size()) {
387 throw InvalidTupleSizeRequested(Size(), sizeof...(T));
388 }
389 detail::RowDataExtractor<T...>::ExtractValues(*this, std::forward<T>(val)...);
390}
391
392template <typename T, typename... Y>
393auto Row::As() const {
394 if constexpr (sizeof...(Y) > 0) {
395 std::tuple<T, Y...> res;
396 To(res, kRowTag);
397 return res;
398 } else {
399 return As<T>(kFieldTag);
400 }
401}
402
403template <typename... T>
404void Row::To(const std::initializer_list<USERVER_NAMESPACE::utils::zstring_view>& names, T&&... val) const {
405 detail::AssertSaneTypeToDeserialize<T...>();
406 if (sizeof...(T) != names.size()) {
407 throw FieldTupleMismatch(names.size(), sizeof...(T));
408 }
409 detail::RowDataExtractor<T...>::ExtractValues(*this, names, std::forward<T>(val)...);
410}
411
412template <typename... T>
413std::tuple<T...> Row::As(const std::initializer_list<USERVER_NAMESPACE::utils::zstring_view>& names) const {
414 if (sizeof...(T) != names.size()) {
415 throw FieldTupleMismatch(names.size(), sizeof...(T));
416 }
417 std::tuple<T...> res;
418 detail::RowDataExtractor<T...>::ExtractTuple(*this, names, res);
419 return res;
420}
421
422template <typename... T>
423void Row::To(const std::initializer_list<size_type>& indexes, T&&... val) const {
424 detail::AssertSaneTypeToDeserialize<T...>();
425 if (sizeof...(T) != indexes.size()) {
426 throw FieldTupleMismatch(indexes.size(), sizeof...(T));
427 }
428 detail::RowDataExtractor<T...>::ExtractValues(*this, indexes, std::forward<T>(val)...);
429}
430
431template <typename... T>
432std::tuple<T...> Row::As(const std::initializer_list<size_type>& indexes) const {
433 if (sizeof...(T) != indexes.size()) {
434 throw FieldTupleMismatch(indexes.size(), sizeof...(T));
435 }
436 std::tuple<T...> res;
437 detail::RowDataExtractor<T...>::ExtractTuple(*this, indexes, res);
438 return res;
439}
440
441} // namespace storages::postgres
442
443USERVER_NAMESPACE_END