userver: userver/storages/postgres/result_set.hpp Source File
Loading...
Searching...
No Matches
result_set.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/result_set.hpp
4/// @brief Result accessors
5
6#include <initializer_list>
7#include <limits>
8#include <memory>
9#include <optional>
10#include <tuple>
11#include <type_traits>
12#include <utility>
13#include <variant>
14
15#include <fmt/format.h>
16
17#include <userver/storages/postgres/exceptions.hpp>
18#include <userver/storages/postgres/io/supported_types.hpp>
19#include <userver/storages/postgres/postgres_fwd.hpp>
20
21#include <userver/storages/postgres/detail/const_data_iterator.hpp>
22
23#include <userver/compiler/demangle.hpp>
24#include <userver/logging/log.hpp>
25
26USERVER_NAMESPACE_BEGIN
27
28namespace storages::postgres {
29
30/// @page pg_process_results uPg: Working with result sets
31///
32/// A result set returned from Execute function is a thin read only wrapper
33/// around the libpq result. It can be copied around as it contains only a
34/// smart pointer to the underlying result set.
35///
36/// The result set's lifetime is not limited by the transaction in which it was
37/// created. In can be used after the transaction is committed or rolled back.
38///
39/// @par Iterating result set's rows
40///
41/// The ResultSet provides interface for range-based iteration over its rows.
42/// @code
43/// auto result = trx.Execute("select foo, bar from foobar");
44/// for (auto row : result) {
45/// // Process row data here
46/// }
47/// @endcode
48///
49/// Also rows can be accessed via indexing operators.
50/// @code
51/// auto result = trx.Execute("select foo, bar from foobar");
52/// for (auto idx = 0; idx < result.Size(); ++idx) {
53/// auto row = result[idx];
54/// // process row data here
55/// }
56/// @endcode
57///
58/// @par Accessing fields in a row
59///
60/// Fields in a row can be accessed by their index, by field name and can be
61/// iterated over. Invalid index or name will throw an exception.
62/// @code
63/// auto f1 = row[0];
64/// auto f2 = row["foo"];
65/// auto f3 = row[1];
66/// auto f4 = row["bar"];
67///
68/// for (auto f : row) {
69/// // Process field here
70/// }
71/// @endcode
72///
73/// @par Extracting field's data to variables
74///
75/// A Field object provides an interface to convert underlying buffer to a
76/// C++ variable of supported type. Please see @ref pg_types for more
77/// information on supported types.
78///
79/// Functions Field::As and Field::To can throw an exception if the field
80/// value is `null`. Their Field::Coalesce counterparts instead set the result
81/// to default value.
82///
83/// All data extraction functions can throw parsing errors (descendants of
84/// ResultSetError).
85///
86/// @code
87/// auto foo = row["foo"].As<int>();
88/// auto bar = row["bar"].As<std::string>();
89///
90/// foo = row["foo"].Coalesce(42);
91/// // There is no parser for char*, so a string object must be passed here.
92/// bar = row["bar"].Coalesce(std::string{"bar"});
93///
94/// row["foo"].To(foo);
95/// row["bar"].To(bar);
96///
97/// row["foo"].Coalesce(foo, 42);
98/// // The type is deduced by the first argument, so the second will be also
99/// // treated as std::string
100/// row["bar"].Coalesce(bar, "baz");
101/// @endcode
102///
103/// @par Extracting data directly from a Row object
104///
105/// Data can be extracted straight from a Row object to a pack or a tuple of
106/// user variables. The number of user variables cannot exceed the number of
107/// fields in the result. If it does, an exception will be thrown.
108///
109/// When used without additional parameters, the field values are extracted
110/// in the order of their appearance.
111///
112/// When a subset of the fields is needed, the fields can be specified by their
113/// indexes or names.
114///
115/// Row's data extraction functions throw exceptions as the field extraction
116/// functions. Also a FieldIndexOutOfBounds or FieldNameDoesntExist can be
117/// thrown.
118///
119/// Statements that return user-defined PostgreSQL type may be called as
120/// returning either one-column row with the whole type in it or as multi-column
121/// row with every column representing a field in the type. For the purpose of
122/// disambiguation, kRowTag may be used.
123///
124/// When a first column is extracted, it is expected that the result set
125/// contains the only column, otherwise an exception will be thrown.
126///
127/// @code
128/// auto [foo, bar] = row.As<int, std::string>();
129/// row.To(foo, bar);
130///
131/// auto [bar, foo] = row.As<std::string, int>({1, 0});
132/// row.To({1, 0}, bar, foo);
133///
134/// auto [bar, foo] = row.As<std::string, int>({"bar", "foo"});
135/// row.To({"bar", "foo"}, bar, foo);
136///
137/// // extract the whole row into a row-type structure.
138/// // The FooBar type must not have the C++ to PostgreSQL mapping in this case
139/// auto foobar = row.As<FooBar>();
140/// row.To(foobar);
141/// // If the FooBar type does have the mapping, the function call must be
142/// // disambiguated.
143/// foobar = row.As<FooBar>(kRowTag);
144/// row.To(foobar, kRowTag);
145/// @endcode
146///
147/// In the following example it is assumed that the row has a single column
148/// and the FooBar type is mapped to a PostgreSQL type.
149///
150/// @note The row is used to extract different types, it doesn't mean it will
151/// actually work with incompatible types.
152///
153/// @code
154/// auto foobar = row.As<FooBar>();
155/// row.To(foobar);
156///
157/// auto str = row.As<std::string>();
158/// auto i = row.As<int>();
159/// @endcode
160///
161///
162/// @par Converting a Row to a user row type
163///
164/// A row can be converted to a user type (tuple, structure, class), for more
165/// information on data type requirements see @ref pg_user_row_types
166///
167/// @todo Interface for converting rows to arbitrary user types
168///
169/// @par Converting ResultSet to a result set with user row types
170///
171/// A result set can be represented as a set of user row types or extracted to
172/// a container. For more information see @ref pg_user_row_types
173///
174/// @todo Interface for copying a ResultSet to an output iterator.
175///
176/// @par Non-select query results
177///
178/// @todo Process non-select result and provide interface. Do the docs.
179///
180///
181/// ----------
182///
183/// @htmlonly <div class="bottom-nav"> @endhtmlonly
184/// ⇦ @ref pg_run_queries | @ref pg_types ⇨
185/// @htmlonly </div> @endhtmlonly
186
188 /// Index of the field in the result set
189 std::size_t index;
190 /// @brief The object ID of the field's data type.
192 /// @brief The field name.
193 // TODO string_view
194 std::string name;
195 /// @brief If the field can be identified as a column of a specific table,
196 /// the object ID of the table; otherwise zero.
198 /// @brief If the field can be identified as a column of a specific table,
199 /// the attribute number of the column; otherwise zero.
201 /// @brief The data type size (see pg_type.typlen). Note that negative
202 /// values denote variable-width types.
203 Integer type_size;
204 /// @brief The type modifier (see pg_attribute.atttypmod). The meaning of
205 /// the modifier is type-specific.
207};
208
209/// @brief A wrapper for PGresult to access field descriptions.
211 public:
212 RowDescription(detail::ResultWrapperPtr res) : res_{std::move(res)} {}
213
214 /// Check that all fields can be read in binary format
215 /// @throw NoBinaryParser if any of the fields doesn't have a binary parser
216 void CheckBinaryFormat(const UserTypes& types) const;
217
218 // TODO interface for iterating field descriptions
219 private:
220 detail::ResultWrapperPtr res_;
221};
222
223class Row;
224class ResultSet;
225template <typename T, typename ExtractionTag>
226class TypedResultSet;
227
228class FieldView final {
229 public:
230 using size_type = std::size_t;
231
232 FieldView(const detail::ResultWrapper& res, size_type row_index,
233 size_type field_index)
234 : res_{res}, row_index_{row_index}, field_index_{field_index} {}
235
236 template <typename T>
237 size_type To(T&& val) const {
238 using ValueType = typename std::decay<T>::type;
239 auto fb = GetBuffer();
240 return ReadNullable(fb, std::forward<T>(val),
241 io::traits::IsNullable<ValueType>{});
242 }
243
244 private:
245 io::FieldBuffer GetBuffer() const;
246 std::string_view Name() const;
247 const io::TypeBufferCategory& GetTypeBufferCategories() const;
248
249 template <typename T>
250 size_type ReadNullable(const io::FieldBuffer& fb, T&& val,
251 std::true_type) const {
252 using ValueType = typename std::decay<T>::type;
253 using NullSetter = io::traits::GetSetNull<ValueType>;
254 if (fb.is_null) {
255 NullSetter::SetNull(val);
256 } else {
257 Read(fb, std::forward<T>(val));
258 }
259 return fb.length;
260 }
261
262 template <typename T>
263 size_type ReadNullable(const io::FieldBuffer& buffer, T&& val,
264 std::false_type) const {
265 if (buffer.is_null) {
266 throw FieldValueIsNull{field_index_, Name(), val};
267 } else {
268 Read(buffer, std::forward<T>(val));
269 }
270 return buffer.length;
271 }
272
273 template <typename T>
274 void Read(const io::FieldBuffer& buffer, T&& val) const {
275 using ValueType = typename std::decay<T>::type;
276 io::traits::CheckParser<ValueType>();
277 try {
278 io::ReadBuffer(buffer, std::forward<T>(val), GetTypeBufferCategories());
279 } catch (ResultSetError& ex) {
280 ex.AddMsgSuffix(
281 fmt::format(" (ResultSet error while reading field #{} name `{}`)",
282 field_index_, Name()));
283 throw;
284 }
285 }
286
287 const detail::ResultWrapper& res_;
288 const size_type row_index_;
289 const size_type field_index_;
290};
291
292/// @brief Accessor to a single field in a result set's row
293class Field {
294 public:
295 using size_type = std::size_t;
296
297 size_type RowIndex() const { return row_index_; }
298 size_type FieldIndex() const { return field_index_; }
299
300 //@{
301 /** @name Field metadata */
302 /// Field name as named in query
304 FieldDescription Description() const;
305
306 Oid GetTypeOid() const;
307 //@}
308
309 //@{
310 /** @name Data access */
311 bool IsNull() const;
312
313 /// Read the field's buffer into user-provided variable.
314 /// @throws FieldValueIsNull If the field is null and the C++ type is
315 /// not nullable.
316 template <typename T>
317 size_type To(T&& val) const {
318 return FieldView{*res_, row_index_, field_index_}.To(std::forward<T>(val));
319 }
320
321 /// Read the field's buffer into user-provided variable.
322 /// If the field is null, set the variable to the default value.
323 template <typename T>
324 void Coalesce(T& val, const T& default_val) const {
325 if (!IsNull())
326 To(val);
327 else
328 val = default_val;
329 }
330
331 /// Convert the field's buffer into a C++ type.
332 /// @throws FieldValueIsNull If the field is null and the C++ type is
333 /// not nullable.
334 template <typename T>
335 typename std::decay<T>::type As() const {
336 T val{};
337 To(val);
338 return val;
339 }
340
341 /// Convert the field's buffer into a C++ type.
342 /// If the field is null, return default value.
343 template <typename T>
344 typename std::decay<T>::type Coalesce(const T& default_val) const {
345 if (IsNull()) return default_val;
346 return As<T>();
347 }
348 //@}
349 const io::TypeBufferCategory& GetTypeBufferCategories() const;
350
351 protected:
352 friend class Row;
353
354 Field() = default;
355
356 Field(detail::ResultWrapperPtr res, size_type row, size_type col)
357 : res_{std::move(res)}, row_index_{row}, field_index_{col} {}
358
359 //@{
360 /** @name Iteration support */
361 bool IsValid() const;
362 int Compare(const Field& rhs) const;
363 std::ptrdiff_t Distance(const Field& rhs) const;
364 Field& Advance(std::ptrdiff_t);
365 //@}
366
367 private:
368 detail::ResultWrapperPtr res_;
369 size_type row_index_{0};
370 size_type field_index_{0};
371};
372
373/// @brief Iterator over fields in a result set's row
377 public:
378 ConstFieldIterator() = default;
379
380 private:
381 friend class Row;
382
383 ConstFieldIterator(detail::ResultWrapperPtr res, size_type row, size_type col)
384 : ConstDataIterator(std::move(res), row, col) {}
385};
386
387/// @brief Reverse iterator over fields in a result set's row
391 public:
392 ReverseConstFieldIterator() = default;
393
394 private:
395 friend class Row;
396
397 ReverseConstFieldIterator(detail::ResultWrapperPtr res, size_type row,
398 size_type col)
399 : ConstDataIterator(std::move(res), row, col) {}
400};
401
402/// Data row in a result set
403/// This class is a mere accessor to underlying result set data buffer,
404/// must not be used outside of result set life scope.
405///
406/// Mimics field container
407class Row {
408 public:
409 //@{
410 /** @name Field container concept */
411 using size_type = std::size_t;
412 using const_iterator = ConstFieldIterator;
413 using const_reverse_iterator = ReverseConstFieldIterator;
414
415 using value_type = Field;
416 using reference = Field;
417 using pointer = const_iterator;
418 //@}
419
420 size_type RowIndex() const { return row_index_; }
421
422 RowDescription GetDescription() const { return {res_}; }
423 //@{
424 /** @name Field container interface */
425 /// Number of fields
426 size_type Size() const;
427
428 //@{
429 /** @name Forward iteration */
430 const_iterator cbegin() const;
431 const_iterator begin() const { return cbegin(); }
432 const_iterator cend() const;
433 const_iterator end() const { return cend(); }
434 //@}
435 //@{
436 /** @name Reverse iteration */
437 const_reverse_iterator crbegin() const;
438 const_reverse_iterator rbegin() const { return crbegin(); }
439 const_reverse_iterator crend() const;
440 const_reverse_iterator rend() const { return crend(); }
441 //@}
442
443 /// @brief Field access by index
444 /// @throws FieldIndexOutOfBounds if index is out of bounds
445 reference operator[](size_type index) const;
446 /// @brief Field access field by name
447 /// @throws FieldNameDoesntExist if the result set doesn't contain
448 /// such a field
449 reference operator[](const std::string& name) const;
450 //@}
451
452 //@{
453 /** @name Access to row's data */
454 /// Read the contents of the row to a user's row type or read the first
455 /// column into the value.
456 ///
457 /// If the user tries to read the first column into a variable, it must be the
458 /// only column in the result set. If the result set contains more than one
459 /// column, the function will throw NonSingleColumnResultSet. If the result
460 /// set is OK to contain more than one columns, the first column value should
461 /// be accessed via `row[0].To/As`.
462 ///
463 /// If the type is a 'row' type, the function will read the fields of the row
464 /// into the type's data members.
465 ///
466 /// If the type can be treated as both a row type and a composite type (the
467 /// type is mapped to a PostgreSQL type), the function will treat the type
468 /// as a type for the first (and the only) column.
469 ///
470 /// To read the all fields of the row as a row type, the To(T&&, RowTag)
471 /// should be used.
472 template <typename T>
473 void To(T&& val) const;
474
475 /// Function to disambiguate reading the row to a user's row type (values
476 /// of the row initialize user's type data members)
477 template <typename T>
478 void To(T&& val, RowTag) const;
479
480 /// Function to disambiguate reading the first column to a user's composite
481 /// type (PostgreSQL composite type in the row initializes user's type).
482 /// The same as calling To(T&& val) for a T mapped to a PostgreSQL type.
483 template <typename T>
484 void To(T&& val, FieldTag) const;
485
486 /// Read fields into variables in order of their appearance in the row
487 template <typename... T>
488 void To(T&&... val) const;
489
490 /// @brief Parse values from the row and return the result.
491 ///
492 /// If there are more than one type arguments to the function, it will
493 /// return a tuple of those types.
494 ///
495 /// If there is a single type argument to the function, it will read the first
496 /// and the only column of the row or the whole row to the row type (depending
497 /// on C++ to PosgreSQL mapping presence) and return plain value of this type.
498 ///
499 /// @see To(T&&)
500 template <typename T, typename... Y>
501 auto As() const;
502
503 /// @brief Returns T initialized with values of the row.
504 /// @snippet storages/postgres/tests/typed_rows_pgtest.cpp RowTagSippet
505 template <typename T>
506 T As(RowTag) const {
507 T val{};
508 To(val, kRowTag);
509 return val;
510 }
511
512 /// @brief Returns T initialized with a single column value of the row.
513 /// @snippet storages/postgres/tests/composite_types_pgtest.cpp FieldTagSippet
514 template <typename T>
515 T As(FieldTag) const {
516 T val{};
517 To(val, kFieldTag);
518 return val;
519 }
520
521 /// Read fields into variables in order of their names in the first argument
522 template <typename... T>
523 void To(const std::initializer_list<std::string>& names, T&&... val) const;
524 template <typename... T>
525 std::tuple<T...> As(const std::initializer_list<std::string>& names) const;
526
527 /// Read fields into variables in order of their indexes in the first
528 /// argument
529 template <typename... T>
530 void To(const std::initializer_list<size_type>& indexes, T&&... val) const;
531 template <typename... T>
532 std::tuple<T...> As(const std::initializer_list<size_type>& indexes) const;
533 //@}
534
535 size_type IndexOfName(const std::string&) const;
536
537 FieldView GetFieldView(size_type index) const;
538
539 protected:
540 friend class ResultSet;
541
542 Row() = default;
543
544 Row(detail::ResultWrapperPtr res, size_type row)
545 : res_{std::move(res)}, row_index_{row} {}
546
547 //@{
548 /** @name Iteration support */
549 bool IsValid() const;
550 int Compare(const Row& rhs) const;
551 std::ptrdiff_t Distance(const Row& rhs) const;
552 Row& Advance(std::ptrdiff_t);
553 //@}
554 private:
555 detail::ResultWrapperPtr res_;
556 size_type row_index_{0};
557};
558
559/// @name Iterator over rows in a result set
563 public:
564 ConstRowIterator() = default;
565
566 private:
567 friend class ResultSet;
568
569 ConstRowIterator(detail::ResultWrapperPtr res, size_type row)
570 : ConstDataIterator(std::move(res), row) {}
571};
572
573/// @name Reverse iterator over rows in a result set
577 public:
578 ReverseConstRowIterator() = default;
579
580 private:
581 friend class ResultSet;
582
583 ReverseConstRowIterator(detail::ResultWrapperPtr res, size_type row)
584 : ConstDataIterator(std::move(res), row) {}
585};
586
587/// @brief PostgreSQL result set
588///
589/// Provides random access to rows via indexing operations
590/// and bidirectional iteration via iterators.
591///
592/// ## Usage synopsis
593/// ```
594/// auto trx = ...;
595/// auto res = trx.Execute("select a, b from table");
596/// for (auto row : res) {
597/// // Process row data
598/// }
599/// ```
601 public:
602 using size_type = std::size_t;
603 using difference_type = std::ptrdiff_t;
604 static constexpr size_type npos = std::numeric_limits<size_type>::max();
605
606 //@{
607 /** @name Row container concept */
608 using const_iterator = ConstRowIterator;
609 using const_reverse_iterator = ReverseConstRowIterator;
610
611 using value_type = Row;
612 using reference = value_type;
613 using pointer = const_iterator;
614 //@}
615
616 explicit ResultSet(std::shared_ptr<detail::ResultWrapper> pimpl)
617 : pimpl_{std::move(pimpl)} {}
618
619 /// Number of rows in the result set
620 size_type Size() const;
621 bool IsEmpty() const { return Size() == 0; }
622
623 size_type RowsAffected() const;
624 std::string CommandStatus() const;
625
626 //@{
627 /** @name Row container interface */
628 //@{
629 /** @name Forward iteration */
630 const_iterator cbegin() const&;
631 const_iterator begin() const& { return cbegin(); }
632 const_iterator cend() const&;
633 const_iterator end() const& { return cend(); }
634
635 // One should store ResultSet before using its accessors
636 const_iterator cbegin() const&& = delete;
637 const_iterator begin() const&& = delete;
638 const_iterator cend() const&& = delete;
639 const_iterator end() const&& = delete;
640 //@}
641 //@{
642 /** @name Reverse iteration */
643 const_reverse_iterator crbegin() const&;
644 const_reverse_iterator rbegin() const& { return crbegin(); }
645 const_reverse_iterator crend() const&;
646 const_reverse_iterator rend() const& { return crend(); }
647 // One should store ResultSet before using its accessors
648 const_reverse_iterator crbegin() const&& = delete;
649 const_reverse_iterator rbegin() const&& = delete;
650 const_reverse_iterator crend() const&& = delete;
651 const_reverse_iterator rend() const&& = delete;
652 //@}
653
654 reference Front() const&;
655 reference Back() const&;
656 // One should store ResultSet before using its accessors
657 reference Front() const&& = delete;
658 reference Back() const&& = delete;
659
660 /// @brief Access a row by index
661 /// @throws RowIndexOutOfBounds if index is out of bounds
662 reference operator[](size_type index) const&;
663 // One should store ResultSet before using its accessors
664 reference operator[](size_type index) const&& = delete;
665 //@}
666
667 //@{
668 /** @name ResultSet metadata access */
669 // TODO ResultSet metadata access interface
670 size_type FieldCount() const;
671 RowDescription GetRowDescription() const& { return {pimpl_}; }
672 // One should store ResultSet before using its accessors
673 RowDescription GetRowDescription() const&& = delete;
674 //@}
675
676 //@{
677 /** @name Typed results */
678 /// @brief Get a wrapper for iterating over a set of typed results.
679 /// For more information see @ref psql_typed_results
680 template <typename T>
681 auto AsSetOf() const;
682 template <typename T>
683 auto AsSetOf(RowTag) const;
684 template <typename T>
685 auto AsSetOf(FieldTag) const;
686
687 /// @brief Extract data into a container.
688 /// For more information see @ref psql_typed_results
689 template <typename Container>
690 Container AsContainer() const;
691 template <typename Container>
692 Container AsContainer(RowTag) const;
693
694 /// @brief Extract first row into user type.
695 /// A single row result set is expected, will throw an exception when result
696 /// set size != 1
697 template <typename T>
698 auto AsSingleRow() const;
699 template <typename T>
700 auto AsSingleRow(RowTag) const;
701 template <typename T>
702 auto AsSingleRow(FieldTag) const;
703
704 /// @brief Extract first row into user type.
705 /// @returns A single row result set if non empty result was returned, empty
706 /// std::optional otherwise
707 /// @throws exception when result set size > 1
708 template <typename T>
709 std::optional<T> AsOptionalSingleRow() const;
710 template <typename T>
711 std::optional<T> AsOptionalSingleRow(RowTag) const;
712 template <typename T>
713 std::optional<T> AsOptionalSingleRow(FieldTag) const;
714 //@}
715 private:
716 friend class detail::ConnectionImpl;
717 void FillBufferCategories(const UserTypes& types);
718 void SetBufferCategoriesFrom(const ResultSet&);
719
720 template <typename T, typename Tag>
721 friend class TypedResultSet;
722 friend class ConnectionImpl;
723
724 std::shared_ptr<detail::ResultWrapper> pimpl_;
725};
726
727namespace detail {
728
729template <typename T>
730struct IsOptionalFromOptional : std::false_type {};
731
732template <typename T>
733struct IsOptionalFromOptional<std::optional<std::optional<T>>>
734 : std::true_type {};
735
736template <typename T>
737struct IsOneVariant : std::false_type {};
738
739template <typename T>
740struct IsOneVariant<std::variant<T>> : std::true_type {};
741
742template <typename... Args>
743constexpr void AssertSaneTypeToDeserialize() {
744 static_assert(
745 !(IsOptionalFromOptional<
746 std::remove_const_t<std::remove_reference_t<Args>>>::value ||
747 ...),
748 "Attempt to get an optional<optional<T>> was detected. Such "
749 "optional-from-optional types are very error prone, obfuscate code and "
750 "are ambiguous to deserialize. Change the type to just optional<T>");
751 static_assert(
752 !(IsOneVariant<
753 std::remove_const_t<std::remove_reference_t<Args>>>::value ||
754 ...),
755 "Attempt to get an variant<T> was detected. Such variant from one type "
756 "obfuscates code. Change the type to just T");
757}
758
759//@{
760/** @name Sequental field extraction */
761template <typename IndexTuple, typename... T>
762struct RowDataExtractorBase;
763
764template <std::size_t... Indexes, typename... T>
765struct RowDataExtractorBase<std::index_sequence<Indexes...>, T...> {
766 static void ExtractValues(const Row& row, T&&... val) {
767 static_assert(sizeof...(Indexes) == sizeof...(T));
768
769 std::size_t field_index = 0;
770 const auto perform = [&](auto&& arg) {
771 row.GetFieldView(field_index++).To(std::forward<decltype(arg)>(arg));
772 };
773 (perform(std::forward<T>(val)), ...);
774 }
775 static void ExtractTuple(const Row& row, std::tuple<T...>& val) {
776 static_assert(sizeof...(Indexes) == sizeof...(T));
777
778 std::size_t field_index = 0;
779 const auto perform = [&](auto& arg) {
780 row.GetFieldView(field_index++).To(arg);
781 };
782 (perform(std::get<Indexes>(val)), ...);
783 }
784 static void ExtractTuple(const Row& row, std::tuple<T...>&& val) {
785 static_assert(sizeof...(Indexes) == sizeof...(T));
786
787 std::size_t field_index = 0;
788 const auto perform = [&](auto& arg) {
789 row.GetFieldView(field_index++).To(arg);
790 };
791 (perform(std::get<Indexes>(val)), ...);
792 }
793
794 static void ExtractValues(const Row& row,
795 const std::initializer_list<std::string>& names,
796 T&&... val) {
797 (row[*(names.begin() + Indexes)].To(std::forward<T>(val)), ...);
798 }
799 static void ExtractTuple(const Row& row,
800 const std::initializer_list<std::string>& names,
801 std::tuple<T...>& val) {
802 std::tuple<T...> tmp{row[*(names.begin() + Indexes)].template As<T>()...};
803 tmp.swap(val);
804 }
805
806 static void ExtractValues(const Row& row,
807 const std::initializer_list<std::size_t>& indexes,
808 T&&... val) {
809 (row[*(indexes.begin() + Indexes)].To(std::forward<T>(val)), ...);
810 }
811 static void ExtractTuple(const Row& row,
812 const std::initializer_list<std::size_t>& indexes,
813 std::tuple<T...>& val) {
814 std::tuple<T...> tmp{row[*(indexes.begin() + Indexes)].template As<T>()...};
815 tmp.swap(val);
816 }
817};
818
819template <typename... T>
820struct RowDataExtractor
821 : RowDataExtractorBase<std::index_sequence_for<T...>, T...> {};
822
823template <typename T>
824struct TupleDataExtractor;
825template <typename... T>
826struct TupleDataExtractor<std::tuple<T...>>
827 : RowDataExtractorBase<std::index_sequence_for<T...>, T...> {};
828//@}
829
830template <typename RowType>
831constexpr void AssertRowTypeIsMappedToPgOrIsCompositeType() {
832 // composite types can be parsed without an explicit mapping
833 static_assert(
834 io::traits::kIsMappedToPg<RowType> ||
835 io::traits::kIsCompositeType<RowType>,
836 "Row type must be mapped to pg type(CppToUserPg) or one of the "
837 "following: "
838 "1. primitive type. "
839 "2. std::tuple. "
840 "3. Aggregation type. See std::aggregation. "
841 "4. Has a Introspect method that makes the std::tuple from your "
842 "class/struct. "
843 "For more info see `uPg: Typed PostgreSQL results` chapter in docs.");
844}
845
846} // namespace detail
847
848template <typename T>
849void Row::To(T&& val) const {
850 To(std::forward<T>(val), kFieldTag);
851}
852
853template <typename T>
854void Row::To(T&& val, RowTag) const {
855 detail::AssertSaneTypeToDeserialize<T>();
856 // Convert the val into a writable tuple and extract the data
857 using ValueType = std::decay_t<T>;
858 io::traits::AssertIsValidRowType<ValueType>();
859 using RowType = io::RowType<ValueType>;
860 using TupleType = typename RowType::TupleType;
861 constexpr auto tuple_size = RowType::size;
862 if (tuple_size > Size()) {
863 throw InvalidTupleSizeRequested(Size(), tuple_size);
864 } else if (tuple_size < Size()) {
866 << "Row size is greater that the number of data members in "
867 "C++ user datatype "
868 << compiler::GetTypeName<T>();
869 }
870
871 detail::TupleDataExtractor<TupleType>::ExtractTuple(
872 *this, RowType::GetTuple(std::forward<T>(val)));
873}
874
875template <typename T>
876void Row::To(T&& val, FieldTag) const {
877 detail::AssertSaneTypeToDeserialize<T>();
878 using ValueType = std::decay_t<T>;
879 detail::AssertRowTypeIsMappedToPgOrIsCompositeType<ValueType>();
880 // Read the first field into the type
881 if (Size() < 1) {
883 }
884 if (Size() > 1) {
885 throw NonSingleColumnResultSet{Size(), compiler::GetTypeName<T>(), "As"};
886 }
887 (*this)[0].To(std::forward<T>(val));
888}
889
890template <typename... T>
891void Row::To(T&&... val) const {
892 detail::AssertSaneTypeToDeserialize<T...>();
893 if (sizeof...(T) > Size()) {
894 throw InvalidTupleSizeRequested(Size(), sizeof...(T));
895 }
896 detail::RowDataExtractor<T...>::ExtractValues(*this, std::forward<T>(val)...);
897}
898
899template <typename T, typename... Y>
900auto Row::As() const {
901 if constexpr (sizeof...(Y) > 0) {
902 std::tuple<T, Y...> res;
903 To(res, kRowTag);
904 return res;
905 } else {
906 return As<T>(kFieldTag);
907 }
908}
909
910template <typename... T>
911void Row::To(const std::initializer_list<std::string>& names,
912 T&&... val) const {
913 detail::AssertSaneTypeToDeserialize<T...>();
914 if (sizeof...(T) != names.size()) {
915 throw FieldTupleMismatch(names.size(), sizeof...(T));
916 }
917 detail::RowDataExtractor<T...>::ExtractValues(*this, names,
918 std::forward<T>(val)...);
919}
920
921template <typename... T>
922std::tuple<T...> Row::As(
923 const std::initializer_list<std::string>& names) const {
924 if (sizeof...(T) != names.size()) {
925 throw FieldTupleMismatch(names.size(), sizeof...(T));
926 }
927 std::tuple<T...> res;
928 detail::RowDataExtractor<T...>::ExtractTuple(*this, names, res);
929 return res;
930}
931
932template <typename... T>
933void Row::To(const std::initializer_list<size_type>& indexes,
934 T&&... val) const {
935 detail::AssertSaneTypeToDeserialize<T...>();
936 if (sizeof...(T) != indexes.size()) {
937 throw FieldTupleMismatch(indexes.size(), sizeof...(T));
938 }
939 detail::RowDataExtractor<T...>::ExtractValues(*this, indexes,
940 std::forward<T>(val)...);
941}
942
943template <typename... T>
944std::tuple<T...> Row::As(
945 const std::initializer_list<size_type>& indexes) const {
946 if (sizeof...(T) != indexes.size()) {
947 throw FieldTupleMismatch(indexes.size(), sizeof...(T));
948 }
949 std::tuple<T...> res;
950 detail::RowDataExtractor<T...>::ExtractTuple(*this, indexes, res);
951 return res;
952}
953
954template <typename T>
955auto ResultSet::AsSetOf() const {
956 return AsSetOf<T>(kFieldTag);
957}
958
959template <typename T>
960auto ResultSet::AsSetOf(RowTag) const {
961 detail::AssertSaneTypeToDeserialize<T>();
962 using ValueType = std::decay_t<T>;
963 io::traits::AssertIsValidRowType<ValueType>();
964 return TypedResultSet<T, RowTag>{*this};
965}
966
967template <typename T>
968auto ResultSet::AsSetOf(FieldTag) const {
969 detail::AssertSaneTypeToDeserialize<T>();
970 using ValueType = std::decay_t<T>;
971 detail::AssertRowTypeIsMappedToPgOrIsCompositeType<ValueType>();
972 if (FieldCount() > 1) {
973 throw NonSingleColumnResultSet{FieldCount(), compiler::GetTypeName<T>(),
974 "AsSetOf"};
975 }
976 return TypedResultSet<T, FieldTag>{*this};
977}
978
979template <typename Container>
980Container ResultSet::AsContainer() const {
981 detail::AssertSaneTypeToDeserialize<Container>();
982 using ValueType = typename Container::value_type;
983 Container c;
984 if constexpr (io::traits::kCanReserve<Container>) {
985 c.reserve(Size());
986 }
987 auto res = AsSetOf<ValueType>();
988
989 auto inserter = io::traits::Inserter(c);
990 auto row_it = res.begin();
991 for (std::size_t i = 0; i < res.Size(); ++i, ++row_it, ++inserter) {
992 *inserter = *row_it;
993 }
994
995 return c;
996}
997
998template <typename Container>
999Container ResultSet::AsContainer(RowTag) const {
1000 detail::AssertSaneTypeToDeserialize<Container>();
1001 using ValueType = typename Container::value_type;
1002 Container c;
1003 if constexpr (io::traits::kCanReserve<Container>) {
1004 c.reserve(Size());
1005 }
1006 auto res = AsSetOf<ValueType>(kRowTag);
1007
1008 auto inserter = io::traits::Inserter(c);
1009 auto row_it = res.begin();
1010 for (std::size_t i = 0; i < res.Size(); ++i, ++row_it, ++inserter) {
1011 *inserter = *row_it;
1012 }
1013
1014 return c;
1015}
1016
1017template <typename T>
1018auto ResultSet::AsSingleRow() const {
1019 return AsSingleRow<T>(kFieldTag);
1020}
1021
1022template <typename T>
1023auto ResultSet::AsSingleRow(RowTag) const {
1024 detail::AssertSaneTypeToDeserialize<T>();
1025 if (Size() != 1) {
1027 }
1028 return Front().As<T>(kRowTag);
1029}
1030
1031template <typename T>
1032auto ResultSet::AsSingleRow(FieldTag) const {
1033 detail::AssertSaneTypeToDeserialize<T>();
1034 if (Size() != 1) {
1036 }
1037 return Front().As<T>(kFieldTag);
1038}
1039
1040template <typename T>
1041std::optional<T> ResultSet::AsOptionalSingleRow() const {
1042 return AsOptionalSingleRow<T>(kFieldTag);
1043}
1044
1045template <typename T>
1046std::optional<T> ResultSet::AsOptionalSingleRow(RowTag) const {
1047 return IsEmpty() ? std::nullopt : std::optional<T>{AsSingleRow<T>(kRowTag)};
1048}
1049
1050template <typename T>
1051std::optional<T> ResultSet::AsOptionalSingleRow(FieldTag) const {
1052 return IsEmpty() ? std::nullopt : std::optional<T>{AsSingleRow<T>(kFieldTag)};
1053}
1054
1055} // namespace storages::postgres
1056
1057USERVER_NAMESPACE_END
1058
1059#include <userver/storages/postgres/typed_result_set.hpp>