userver: userver/storages/postgres/result_set.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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 <limits>
7#include <memory>
8#include <optional>
9#include <type_traits>
10#include <utility>
11
12#include <fmt/format.h>
13
14#include <userver/storages/postgres/detail/typed_rows.hpp>
15#include <userver/storages/postgres/exceptions.hpp>
16#include <userver/storages/postgres/io/supported_types.hpp>
17#include <userver/storages/postgres/row.hpp>
18
19#include <userver/compiler/demangle.hpp>
20#include <userver/logging/log.hpp>
21
22USERVER_NAMESPACE_BEGIN
23
24namespace storages::postgres {
25
26/// @page pg_process_results uPg: Working with result sets
27///
28/// A result set returned from Execute function is a thin read only wrapper
29/// around the libpq result. It can be copied around as it contains only a
30/// smart pointer to the underlying result set.
31///
32/// The result set's lifetime is not limited by the transaction in which it was
33/// created. In can be used after the transaction is committed or rolled back.
34///
35/// @par Iterating result set's rows
36///
37/// The ResultSet provides interface for range-based iteration over its rows.
38/// @code
39/// auto result = trx.Execute("select foo, bar from foobar");
40/// for (auto row : result) {
41/// // Process row data here
42/// }
43/// @endcode
44///
45/// Also rows can be accessed via indexing operators.
46/// @code
47/// auto result = trx.Execute("select foo, bar from foobar");
48/// for (auto idx = 0; idx < result.Size(); ++idx) {
49/// auto row = result[idx];
50/// // process row data here
51/// }
52/// @endcode
53///
54/// @par Accessing fields in a row
55///
56/// Fields in a row can be accessed by their index, by field name and can be
57/// iterated over. Invalid index or name will throw an exception.
58/// @code
59/// auto f1 = row[0];
60/// auto f2 = row["foo"];
61/// auto f3 = row[1];
62/// auto f4 = row["bar"];
63///
64/// for (auto f : row) {
65/// // Process field here
66/// }
67/// @endcode
68///
69/// @par Extracting field's data to variables
70///
71/// A Field object provides an interface to convert underlying buffer to a
72/// C++ variable of supported type. Please see
73/// @ref scripts/docs/en/userver/pg_types.md for more information on supported
74/// types.
75///
76/// Functions Field::As and Field::To can throw an exception if the field
77/// value is `null`. Their Field::Coalesce counterparts instead set the result
78/// to default value.
79///
80/// All data extraction functions can throw parsing errors (descendants of
81/// ResultSetError).
82///
83/// @code
84/// auto foo = row["foo"].As<int>();
85/// auto bar = row["bar"].As<std::string>();
86///
87/// foo = row["foo"].Coalesce(42);
88/// // There is no parser for char*, so a string object must be passed here.
89/// bar = row["bar"].Coalesce(std::string{"bar"});
90///
91/// row["foo"].To(foo);
92/// row["bar"].To(bar);
93///
94/// row["foo"].Coalesce(foo, 42);
95/// // The type is deduced by the first argument, so the second will be also
96/// // treated as std::string
97/// row["bar"].Coalesce(bar, "baz");
98/// @endcode
99///
100/// @par Extracting data directly from a Row object
101///
102/// Data can be extracted straight from a Row object to a pack or a tuple of
103/// user variables. The number of user variables cannot exceed the number of
104/// fields in the result. If it does, an exception will be thrown.
105///
106/// When used without additional parameters, the field values are extracted
107/// in the order of their appearance.
108///
109/// When a subset of the fields is needed, the fields can be specified by their
110/// indexes or names.
111///
112/// Row's data extraction functions throw exceptions as the field extraction
113/// functions. Also a FieldIndexOutOfBounds or FieldNameDoesntExist can be
114/// thrown.
115///
116/// Statements that return user-defined PostgreSQL type may be called as
117/// returning either one-column row with the whole type in it or as multi-column
118/// row with every column representing a field in the type. For the purpose of
119/// disambiguation, kRowTag may be used.
120///
121/// When a first column is extracted, it is expected that the result set
122/// contains the only column, otherwise an exception will be thrown.
123///
124/// @code
125/// auto [foo, bar] = row.As<int, std::string>();
126/// row.To(foo, bar);
127///
128/// auto [bar, foo] = row.As<std::string, int>({1, 0});
129/// row.To({1, 0}, bar, foo);
130///
131/// auto [bar, foo] = row.As<std::string, int>({"bar", "foo"});
132/// row.To({"bar", "foo"}, bar, foo);
133///
134/// // extract the whole row into a row-type structure.
135/// // The FooBar type must not have the C++ to PostgreSQL mapping in this case
136/// auto foobar = row.As<FooBar>();
137/// row.To(foobar);
138/// // If the FooBar type does have the mapping, the function call must be
139/// // disambiguated.
140/// foobar = row.As<FooBar>(kRowTag);
141/// row.To(foobar, kRowTag);
142/// @endcode
143///
144/// In the following example it is assumed that the row has a single column
145/// and the FooBar type is mapped to a PostgreSQL type.
146///
147/// @note The row is used to extract different types, it doesn't mean it will
148/// actually work with incompatible types.
149///
150/// @code
151/// auto foobar = row.As<FooBar>();
152/// row.To(foobar);
153///
154/// auto str = row.As<std::string>();
155/// auto i = row.As<int>();
156/// @endcode
157///
158///
159/// @par Converting a Row to a user row type
160///
161/// A row can be converted to a user type (tuple, structure, class), for more
162/// information on data type requirements see @ref pg_user_row_types
163///
164/// @todo Interface for converting rows to arbitrary user types
165///
166/// @par Converting ResultSet to a result set with user row types
167///
168/// A result set can be represented as a set of user row types or extracted to
169/// a container. For more information see @ref pg_user_row_types
170///
171/// @todo Interface for copying a ResultSet to an output iterator.
172///
173/// @par Non-select query results
174///
175/// @todo Process non-select result and provide interface. Do the docs.
176///
177///
178/// ----------
179///
180/// @htmlonly <div class="bottom-nav"> @endhtmlonly
181/// ⇦ @ref pg_run_queries | @ref scripts/docs/en/userver/pg_types.md ⇨
182/// @htmlonly </div> @endhtmlonly
183
184template <typename T, typename ExtractionTag>
185class TypedResultSet;
186
187/// @brief PostgreSQL result set
188///
189/// Provides random access to rows via indexing operations
190/// and bidirectional iteration via iterators.
191///
192/// ## Usage synopsis
193/// ```
194/// auto trx = ...;
195/// auto res = trx.Execute("select a, b from table");
196/// for (auto row : res) {
197/// // Process row data
198/// }
199/// ```
201public:
202 using size_type = std::size_t;
203 using difference_type = std::ptrdiff_t;
204 static constexpr size_type npos = std::numeric_limits<size_type>::max();
205
206 //@{
207 /** @name Row container concept */
208 using const_iterator = ConstRowIterator;
209 using const_reverse_iterator = ReverseConstRowIterator;
210
211 using value_type = Row;
212 using reference = value_type;
213 using pointer = const_iterator;
214 //@}
215
216 explicit ResultSet(std::shared_ptr<detail::ResultWrapper> pimpl) : pimpl_{std::move(pimpl)} {}
217
218 /// Number of rows in the result set
219 size_type Size() const;
220 bool IsEmpty() const { return Size() == 0; }
221
222 size_type RowsAffected() const;
223 std::string CommandStatus() const;
224
225 //@{
226 /** @name Row container interface */
227 //@{
228 /** @name Forward iteration */
229 const_iterator cbegin() const&;
230 const_iterator begin() const& { return cbegin(); }
231 const_iterator cend() const&;
232 const_iterator end() const& { return cend(); }
233
234 // One should store ResultSet before using its accessors
235 const_iterator cbegin() const&& = delete;
236 const_iterator begin() const&& = delete;
237 const_iterator cend() const&& = delete;
238 const_iterator end() const&& = delete;
239 //@}
240 //@{
241 /** @name Reverse iteration */
242 const_reverse_iterator crbegin() const&;
243 const_reverse_iterator rbegin() const& { return crbegin(); }
244 const_reverse_iterator crend() const&;
245 const_reverse_iterator rend() const& { return crend(); }
246 // One should store ResultSet before using its accessors
247 const_reverse_iterator crbegin() const&& = delete;
248 const_reverse_iterator rbegin() const&& = delete;
249 const_reverse_iterator crend() const&& = delete;
250 const_reverse_iterator rend() const&& = delete;
251 //@}
252
253 reference Front() const&;
254 reference Back() const&;
255 // One should store ResultSet before using its accessors
256 reference Front() const&& = delete;
257 reference Back() const&& = delete;
258
259 /// @brief Access a row by index
260 /// @throws RowIndexOutOfBounds if index is out of bounds
261 reference operator[](size_type index) const&;
262 // One should store ResultSet before using its accessors
263 reference operator[](size_type index) const&& = delete;
264 //@}
265
266 //@{
267 /** @name ResultSet metadata access */
268 // TODO ResultSet metadata access interface
269 size_type FieldCount() const;
270 RowDescription GetRowDescription() const& { return {pimpl_}; }
271 // One should store ResultSet before using its accessors
272 RowDescription GetRowDescription() const&& = delete;
273 //@}
274
275 //@{
276 /** @name Typed results */
277 /// @brief Get a wrapper for iterating over a set of typed results.
278 /// For more information see @ref pg_user_row_types
279 template <typename T>
280 auto AsSetOf() const;
281 template <typename T>
282 auto AsSetOf(RowTag) const;
283 template <typename T>
284 auto AsSetOf(FieldTag) const;
285
286 /// @brief Extract data into a container.
287 /// For more information see @ref pg_user_row_types
288 template <typename Container>
289 Container AsContainer() const;
290 template <typename Container>
291 Container AsContainer(RowTag) const;
292
293 /// @brief Extract first row into user type.
294 /// A single row result set is expected, will throw an exception when result
295 /// set size != 1
296 template <typename T>
297 auto AsSingleRow() const;
298 template <typename T>
299 auto AsSingleRow(RowTag) const;
300 template <typename T>
301 auto AsSingleRow(FieldTag) const;
302
303 /// @brief Extract first row into user type.
304 /// @returns A single row result set if non empty result was returned, empty
305 /// std::optional otherwise
306 /// @throws exception when result set size > 1
307 template <typename T>
308 std::optional<T> AsOptionalSingleRow() const;
309 template <typename T>
310 std::optional<T> AsOptionalSingleRow(RowTag) const;
311 template <typename T>
312 std::optional<T> AsOptionalSingleRow(FieldTag) const;
313 //@}
314private:
315 friend class detail::ConnectionImpl;
316 void FillBufferCategories(const UserTypes& types);
317 void SetBufferCategoriesFrom(const ResultSet&);
318
319 template <typename T, typename Tag>
320 friend class TypedResultSet;
321 friend class ConnectionImpl;
322
323 std::shared_ptr<detail::ResultWrapper> pimpl_;
324};
325
326template <typename T>
327auto ResultSet::AsSetOf() const {
328 return AsSetOf<T>(kFieldTag);
329}
330
331template <typename T>
332auto ResultSet::AsSetOf(RowTag) const {
333 detail::AssertSaneTypeToDeserialize<T>();
334 using ValueType = std::decay_t<T>;
335 io::traits::AssertIsValidRowType<ValueType>();
336 return TypedResultSet<T, RowTag>{*this};
337}
338
339template <typename T>
340auto ResultSet::AsSetOf(FieldTag) const {
341 detail::AssertSaneTypeToDeserialize<T>();
342 using ValueType = std::decay_t<T>;
343 detail::AssertRowTypeIsMappedToPgOrIsCompositeType<ValueType>();
344 if (FieldCount() > 1) {
345 throw NonSingleColumnResultSet{FieldCount(), compiler::GetTypeName<T>(), "AsSetOf"};
346 }
347 return TypedResultSet<T, FieldTag>{*this};
348}
349
350template <typename Container>
351Container ResultSet::AsContainer() const {
352 detail::AssertSaneTypeToDeserialize<Container>();
353 using ValueType = typename Container::value_type;
354 Container c;
355 if constexpr (io::traits::kCanReserve<Container>) {
356 c.reserve(Size());
357 }
358 auto res = AsSetOf<ValueType>();
359
360 auto inserter = io::traits::Inserter(c);
361 auto row_it = res.begin();
362 for (std::size_t i = 0; i < res.Size(); ++i, ++row_it, ++inserter) {
363 *inserter = *row_it;
364 }
365
366 return c;
367}
368
369template <typename Container>
370Container ResultSet::AsContainer(RowTag) const {
371 detail::AssertSaneTypeToDeserialize<Container>();
372 using ValueType = typename Container::value_type;
373 Container c;
374 if constexpr (io::traits::kCanReserve<Container>) {
375 c.reserve(Size());
376 }
377 auto res = AsSetOf<ValueType>(kRowTag);
378
379 auto inserter = io::traits::Inserter(c);
380 auto row_it = res.begin();
381 for (std::size_t i = 0; i < res.Size(); ++i, ++row_it, ++inserter) {
382 *inserter = *row_it;
383 }
384
385 return c;
386}
387
388template <typename T>
389auto ResultSet::AsSingleRow() const {
390 return AsSingleRow<T>(kFieldTag);
391}
392
393template <typename T>
394auto ResultSet::AsSingleRow(RowTag) const {
395 detail::AssertSaneTypeToDeserialize<T>();
396 if (Size() != 1) {
398 }
399 return Front().As<T>(kRowTag);
400}
401
402template <typename T>
403auto ResultSet::AsSingleRow(FieldTag) const {
404 detail::AssertSaneTypeToDeserialize<T>();
405 if (Size() != 1) {
407 }
408 return Front().As<T>(kFieldTag);
409}
410
411template <typename T>
412std::optional<T> ResultSet::AsOptionalSingleRow() const {
413 return AsOptionalSingleRow<T>(kFieldTag);
414}
415
416template <typename T>
417std::optional<T> ResultSet::AsOptionalSingleRow(RowTag) const {
418 return IsEmpty() ? std::nullopt : std::optional<T>{AsSingleRow<T>(kRowTag)};
419}
420
421template <typename T>
422std::optional<T> ResultSet::AsOptionalSingleRow(FieldTag) const {
423 return IsEmpty() ? std::nullopt : std::optional<T>{AsSingleRow<T>(kFieldTag)};
424}
425
426/// @page pg_user_row_types uPg: Typed PostgreSQL results
427///
428/// The ResultSet provides access to a generic PostgreSQL result buffer wrapper
429/// with access to individual column buffers and means to parse the buffers into
430/// a certain type.
431///
432/// For a user that wishes to get the results in a form of a sequence or a
433/// container of C++ tuples or structures, the driver provides a way to coerce
434/// the generic result set into a typed result set or a container of tuples or
435/// structures that fulfill certain conditions.
436///
437/// TypedResultSet provides container interface for typed result rows for
438/// iteration or random access without converting all the result set at once.
439/// The iterators in the TypedResultSet satisfy requirements for a constant
440/// RandomAccessIterator with the exception of dereferencing iterators.
441///
442/// @warning The operator* of the iterators returns value (not a reference to
443/// it) and the iterators don't have the operator->.
444///
445/// @par Data row extraction
446///
447/// The data rows can be obtained as:
448/// - std::tuple;
449/// - aggregate class as is;
450/// - non-aggregate class with some augmentation.
451///
452/// Data members of the tuple or the classes must be supported by the driver.
453/// For more information on supported data types please see
454/// @ref scripts/docs/en/userver/pg_types.md.
455///
456/// @par std::tuple.
457///
458/// The first option is to convert ResultSet's row to std::tuples.
459///
460/// ```
461/// using MyRowType = std::tuple<int, string>;
462/// auto trx = ...;
463/// auto generic_result = trx.Execute("select a, b from my_table");
464/// auto iteration = generic_result.AsSetOf<MyRowType>();
465/// for (auto row : iteration) {
466/// static_assert(std::is_same_v<decltype(row), MyRowType>,
467/// "Iterate over tuples");
468/// auto [a, b] = row;
469/// std::cout << "a = " << a << "; b = " << b << "\n";
470/// }
471///
472/// auto data = geric_result.AsContainer<std::vector<MyRowType>>();
473/// ```
474///
475/// @par Aggregate classes.
476///
477/// A data row can be coerced to an aggregate class.
478///
479/// An aggregate class (C++03 8.5.1 §1) is a class that with no base classes, no
480/// protected or private non-static data members, no user-declared constructors
481/// and no virtual functions.
482///
483/// ```
484/// struct MyRowType {
485/// int a;
486/// std::string b;
487/// };
488/// auto generic_result = trx.Execute("select a, b from my_table");
489/// auto iteration = generic_result.AsSetOf<MyRowType>();
490/// for (auto row : iteration) {
491/// static_assert(std::is_same_v<decltype(row), MyRowType>,
492/// "Iterate over aggregate classes");
493/// std::cout << "a = " << row.a << "; b = " << row.b << "\n";
494/// }
495///
496/// auto data = geric_result.AsContainer<std::vector<MyRowType>>();
497/// ```
498///
499/// @par Non-aggregate classes.
500///
501/// Classes that do not satisfy the aggregate class requirements can be used
502/// to be created from data rows by providing additional `Introspect` non-static
503/// member function. The function should return a tuple of references to
504/// member data fields. The class must be default constructible.
505///
506/// ```
507/// class MyRowType {
508/// private:
509/// int a_;
510/// std::string b_;
511/// public:
512/// MyRowType() = default; // default ctor is required
513/// explicit MyRowType(int x);
514///
515/// auto Introspect() {
516/// return std::tie(a_, b_);
517/// }
518/// int GetA() const;
519/// const std::string& GetB() const;
520/// };
521///
522/// auto generic_result = trx.Execute("select a, b from my_table");
523/// auto iteration = generic_result.AsSetOf<MyRowType>();
524/// for (auto row : iteration) {
525/// static_assert(std::is_same_v<decltype(row), MyRowType>,
526/// "Iterate over non-aggregate classes");
527/// std::cout << "a = " << row.GetA() << "; b = " << row.GetB() << "\n";
528/// }
529///
530/// auto data = geric_result.AsContainer<std::vector<MyRowType>>();
531/// ```
532/// @par Single-column result set
533///
534/// A single-column result set can be used to extract directly to the column
535/// type. User types mapped to PostgreSQL will work as well. If you need to
536/// extract the whole row into such a structure, you will need to disambiguate
537/// the call with the kRowTag.
538///
539/// @code
540/// auto string_set = generic_result.AsSetOf<std::string>();
541/// std::string s = string_set[0];
542///
543/// auto string_vec = generic_result.AsContainer<std::vector<std::string>>();
544///
545/// // Extract first column into the composite type
546/// auto foo_set = generic_result.AsSetOf<FooBar>();
547/// auto foo_vec = generic_result.AsContainer<std::vector<FooBar>>();
548///
549/// // Extract the whole row, disambiguation
550/// auto foo_set = generic_result.AsSetOf<FooBar>(kRowTag);
551///
552/// @endcode
553///
554///
555/// ----------
556///
557/// @htmlonly <div class="bottom-nav"> @endhtmlonly
558/// ⇦ @ref scripts/docs/en/userver/pg_types.md | @ref pg_errors ⇨
559/// @htmlonly </div> @endhtmlonly
560
561template <typename T, typename ExtractionTag>
562class TypedResultSet {
563public:
564 using size_type = ResultSet::size_type;
565 using difference_type = ResultSet::difference_type;
566 static constexpr size_type npos = ResultSet::npos;
567 static constexpr ExtractionTag kExtractTag{};
568
569 //@{
570 /** @name Row container concept */
571 using const_iterator = detail::ConstTypedRowIterator<T, ExtractionTag, detail::IteratorDirection::kForward>;
572 using const_reverse_iterator = detail::ConstTypedRowIterator<T, ExtractionTag, detail::IteratorDirection::kReverse>;
573
574 using value_type = T;
575 using pointer = const_iterator;
576
577// Forbidding assignments to operator[] result in debug, getting max
578// performance in release.
579#ifdef NDEBUG
580 using reference = value_type;
581#else
582 using reference = std::add_const_t<value_type>;
583#endif
584
585 //@}
586 explicit TypedResultSet(ResultSet result) : result_{std::move(result)} {}
587
588 /// Number of rows in the result set
589 size_type Size() const { return result_.Size(); }
590 bool IsEmpty() const { return Size() == 0; }
591 //@{
592 /** @name Container interface */
593 //@{
594 /** @name Row container interface */
595 //@{
596 /** @name Forward iteration */
597 const_iterator cbegin() const& { return const_iterator{result_.pimpl_, 0}; }
598 const_iterator begin() const& { return cbegin(); }
599 const_iterator cend() const& { return const_iterator{result_.pimpl_, Size()}; }
600 const_iterator end() const& { return cend(); }
601 const_iterator cbegin() const&& { ReportMisuse(); }
602 const_iterator begin() const&& { ReportMisuse(); }
603 const_iterator cend() const&& { ReportMisuse(); }
604 const_iterator end() const&& { ReportMisuse(); }
605 //@}
606 //@{
607 /** @name Reverse iteration */
608 const_reverse_iterator crbegin() const& { return const_reverse_iterator(result_.pimpl_, Size() - 1); }
609 const_reverse_iterator rbegin() const& { return crbegin(); }
610 const_reverse_iterator crend() const& { return const_reverse_iterator(result_.pimpl_, npos); }
611 const_reverse_iterator rend() const& { return crend(); }
612 const_reverse_iterator crbegin() const&& { ReportMisuse(); }
613 const_reverse_iterator rbegin() const&& { ReportMisuse(); }
614 const_reverse_iterator crend() const&& { ReportMisuse(); }
615 const_reverse_iterator rend() const&& { ReportMisuse(); }
616 //@}
617 /// @brief Access a row by index
618 /// @throws RowIndexOutOfBounds if index is out of bounds
619 // NOLINTNEXTLINE(readability-const-return-type)
620 reference operator[](size_type index) const& { return result_[index].template As<value_type>(kExtractTag); }
621 // NOLINTNEXTLINE(readability-const-return-type)
622 reference operator[](size_type) const&& { ReportMisuse(); }
623 //@}
624private:
625 [[noreturn]] static void ReportMisuse() {
626 static_assert(!sizeof(T), "keep the TypedResultSet before using, please");
627 }
628
629 ResultSet result_;
630};
631
632} // namespace storages::postgres
633
634USERVER_NAMESPACE_END