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