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