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