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 <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)
217 : pimpl_{std::move(pimpl)}
218 {}
219
220 /// Number of rows in the result set
221 size_type Size() const;
222 bool IsEmpty() const { return Size() == 0; }
223
224 size_type RowsAffected() const;
225 std::string CommandStatus() const;
226
227 //@{
228 /** @name Row container interface */
229 //@{
230 /** @name Forward iteration */
231 const_iterator cbegin() const&;
232 const_iterator begin() const& { return cbegin(); }
233 const_iterator cend() const&;
234 const_iterator end() const& { return cend(); }
235
236 // One should store ResultSet before using its accessors
237 const_iterator cbegin() const&& = delete;
238 const_iterator begin() const&& = delete;
239 const_iterator cend() const&& = delete;
240 const_iterator end() const&& = delete;
241 //@}
242 //@{
243 /** @name Reverse iteration */
244 const_reverse_iterator crbegin() const&;
245 const_reverse_iterator rbegin() const& { return crbegin(); }
246 const_reverse_iterator crend() const&;
247 const_reverse_iterator rend() const& { return crend(); }
248 // One should store ResultSet before using its accessors
249 const_reverse_iterator crbegin() const&& = delete;
250 const_reverse_iterator rbegin() const&& = delete;
251 const_reverse_iterator crend() const&& = delete;
252 const_reverse_iterator rend() const&& = delete;
253 //@}
254
255 reference Front() const&;
256 reference Back() const&;
257 // One should store ResultSet before using its accessors
258 reference Front() const&& = delete;
259 reference Back() const&& = delete;
260
261 /// @brief Access a row by index
262 /// @throws RowIndexOutOfBounds if index is out of bounds
263 reference operator[](size_type index) const&;
264 // One should store ResultSet before using its accessors
265 reference operator[](size_type index) const&& = delete;
266 //@}
267
268 //@{
269 /** @name ResultSet metadata access */
270 // TODO ResultSet metadata access interface
271 size_type FieldCount() const;
272 RowDescription GetRowDescription() const& { return {pimpl_}; }
273 // One should store ResultSet before using its accessors
274 RowDescription GetRowDescription() const&& = delete;
275 //@}
276
277 //@{
278 /** @name Typed results */
279 /// @brief Get a wrapper for iterating over a set of typed results.
280 /// For more information see @ref pg_user_row_types
281 template <typename T>
282 auto AsSetOf() const;
283 template <typename T>
284 auto AsSetOf(RowTag) const;
285 template <typename T>
286 auto AsSetOf(FieldTag) const;
287
288 /// @brief Extract data into a container.
289 /// For more information see @ref pg_user_row_types
290 template <typename Container>
291 Container AsContainer() const;
292 template <typename Container>
293 Container AsContainer(RowTag) const;
294
295 /// @brief Extract first row into user type.
296 /// A single row result set is expected, will throw an exception when result
297 /// set size != 1
298 template <typename T>
299 auto AsSingleRow() const;
300 template <typename T>
301 auto AsSingleRow(RowTag) const;
302 template <typename T>
303 auto AsSingleRow(FieldTag) const;
304
305 /// @brief Extract first row into user type.
306 /// @returns A single row result set if non empty result was returned, empty
307 /// std::optional otherwise
308 /// @throws exception when result set size > 1
309 template <typename T>
310 std::optional<T> AsOptionalSingleRow() const;
311 template <typename T>
312 std::optional<T> AsOptionalSingleRow(RowTag) const;
313 template <typename T>
314 std::optional<T> AsOptionalSingleRow(FieldTag) const;
315 //@}
316private:
317 friend class detail::ConnectionImpl;
318 void FillBufferCategories(const UserTypes& types);
319 void SetBufferCategoriesFrom(const ResultSet&);
320
321 template <typename T, typename Tag>
322 friend class TypedResultSet;
323 friend class ConnectionImpl;
324
325 std::shared_ptr<detail::ResultWrapper> pimpl_;
326};
327
328template <typename T>
329auto ResultSet::AsSetOf() const {
330 return AsSetOf<T>(kFieldTag);
331}
332
333template <typename T>
334auto ResultSet::AsSetOf(RowTag) const {
335 detail::AssertSaneTypeToDeserialize<T>();
336 using ValueType = std::decay_t<T>;
337 io::traits::AssertIsValidRowType<ValueType>();
338 return TypedResultSet<T, RowTag>{*this};
339}
340
341template <typename T>
342auto ResultSet::AsSetOf(FieldTag) const {
343 detail::AssertSaneTypeToDeserialize<T>();
344 using ValueType = std::decay_t<T>;
345 detail::AssertRowTypeIsMappedToPgOrIsCompositeType<ValueType>();
346 if (FieldCount() > 1) {
347 throw NonSingleColumnResultSet{FieldCount(), compiler::GetTypeName<T>(), "AsSetOf"};
348 }
349 return TypedResultSet<T, FieldTag>{*this};
350}
351
352template <typename Container>
353Container ResultSet::AsContainer() const {
354 detail::AssertSaneTypeToDeserialize<Container>();
355 using ValueType = typename Container::value_type;
356 Container c;
357 if constexpr (io::traits::kCanReserve<Container>) {
358 c.reserve(Size());
359 }
360 auto res = AsSetOf<ValueType>();
361
362 auto inserter = io::traits::Inserter(c);
363 auto row_it = res.begin();
364 for (std::size_t i = 0; i < res.Size(); ++i, ++row_it, ++inserter) {
365 *inserter = *row_it;
366 }
367
368 return c;
369}
370
371template <typename Container>
372Container ResultSet::AsContainer(RowTag) const {
373 detail::AssertSaneTypeToDeserialize<Container>();
374 using ValueType = typename Container::value_type;
375 Container c;
376 if constexpr (io::traits::kCanReserve<Container>) {
377 c.reserve(Size());
378 }
379 auto res = AsSetOf<ValueType>(kRowTag);
380
381 auto inserter = io::traits::Inserter(c);
382 auto row_it = res.begin();
383 for (std::size_t i = 0; i < res.Size(); ++i, ++row_it, ++inserter) {
384 *inserter = *row_it;
385 }
386
387 return c;
388}
389
390template <typename T>
391auto ResultSet::AsSingleRow() const {
392 return AsSingleRow<T>(kFieldTag);
393}
394
395template <typename T>
396auto ResultSet::AsSingleRow(RowTag) const {
397 detail::AssertSaneTypeToDeserialize<T>();
398 if (Size() != 1) {
400 }
401 return Front().As<T>(kRowTag);
402}
403
404template <typename T>
405auto ResultSet::AsSingleRow(FieldTag) const {
406 detail::AssertSaneTypeToDeserialize<T>();
407 if (Size() != 1) {
409 }
410 return Front().As<T>(kFieldTag);
411}
412
413template <typename T>
414std::optional<T> ResultSet::AsOptionalSingleRow() const {
415 return AsOptionalSingleRow<T>(kFieldTag);
416}
417
418template <typename T>
419std::optional<T> ResultSet::AsOptionalSingleRow(RowTag) const {
420 return IsEmpty() ? std::nullopt : std::optional<T>{AsSingleRow<T>(kRowTag)};
421}
422
423template <typename T>
424std::optional<T> ResultSet::AsOptionalSingleRow(FieldTag) const {
425 return IsEmpty() ? std::nullopt : std::optional<T>{AsSingleRow<T>(kFieldTag)};
426}
427
428/// @page pg_user_row_types uPg: Typed PostgreSQL results
429///
430/// The ResultSet provides access to a generic PostgreSQL result buffer wrapper
431/// with access to individual column buffers and means to parse the buffers into
432/// a certain type.
433///
434/// For a user that wishes to get the results in a form of a sequence or a
435/// container of C++ tuples or structures, the driver provides a way to coerce
436/// the generic result set into a typed result set or a container of tuples or
437/// structures that fulfill certain conditions.
438///
439/// TypedResultSet provides container interface for typed result rows for
440/// iteration or random access without converting all the result set at once.
441/// The iterators in the TypedResultSet satisfy requirements for a constant
442/// RandomAccessIterator with the exception of dereferencing iterators.
443///
444/// @warning The operator* of the iterators returns value (not a reference to
445/// it) and the iterators don't have the operator->.
446///
447/// @par Data row extraction
448///
449/// The data rows can be obtained as:
450/// - std::tuple;
451/// - aggregate class as is;
452/// - non-aggregate class with some augmentation.
453///
454/// Data members of the tuple or the classes must be supported by the driver.
455/// For more information on supported data types please see
456/// @ref scripts/docs/en/userver/pg_types.md.
457///
458/// @par std::tuple.
459///
460/// The first option is to convert ResultSet's row to std::tuples.
461///
462/// ```
463/// using MyRowType = std::tuple<int, string>;
464/// auto trx = ...;
465/// auto generic_result = trx.Execute("select a, b from my_table");
466/// auto iteration = generic_result.AsSetOf<MyRowType>();
467/// for (auto row : iteration) {
468/// static_assert(std::is_same_v<decltype(row), MyRowType>,
469/// "Iterate over tuples");
470/// auto [a, b] = row;
471/// std::cout << "a = " << a << "; b = " << b << "\n";
472/// }
473///
474/// auto data = geric_result.AsContainer<std::vector<MyRowType>>();
475/// ```
476///
477/// @par Aggregate classes.
478///
479/// A data row can be coerced to an aggregate class.
480///
481/// An aggregate class (C++03 8.5.1 §1) is a class that with no base classes, no
482/// protected or private non-static data members, no user-declared constructors
483/// and no virtual functions.
484///
485/// ```
486/// struct MyRowType {
487/// int a;
488/// std::string b;
489/// };
490/// auto generic_result = trx.Execute("select a, b from my_table");
491/// auto iteration = generic_result.AsSetOf<MyRowType>();
492/// for (auto row : iteration) {
493/// static_assert(std::is_same_v<decltype(row), MyRowType>,
494/// "Iterate over aggregate classes");
495/// std::cout << "a = " << row.a << "; b = " << row.b << "\n";
496/// }
497///
498/// auto data = geric_result.AsContainer<std::vector<MyRowType>>();
499/// ```
500///
501/// @par Non-aggregate classes.
502///
503/// Classes that do not satisfy the aggregate class requirements can be used
504/// to be created from data rows by providing additional `Introspect` non-static
505/// member function. The function should return a tuple of references to
506/// member data fields. The class must be default constructible.
507///
508/// ```
509/// class MyRowType {
510/// private:
511/// int a_;
512/// std::string b_;
513/// public:
514/// MyRowType() = default; // default ctor is required
515/// explicit MyRowType(int x);
516///
517/// auto Introspect() {
518/// return std::tie(a_, b_);
519/// }
520/// int GetA() const;
521/// const std::string& GetB() const;
522/// };
523///
524/// auto generic_result = trx.Execute("select a, b from my_table");
525/// auto iteration = generic_result.AsSetOf<MyRowType>();
526/// for (auto row : iteration) {
527/// static_assert(std::is_same_v<decltype(row), MyRowType>,
528/// "Iterate over non-aggregate classes");
529/// std::cout << "a = " << row.GetA() << "; b = " << row.GetB() << "\n";
530/// }
531///
532/// auto data = geric_result.AsContainer<std::vector<MyRowType>>();
533/// ```
534/// @par Single-column result set
535///
536/// A single-column result set can be used to extract directly to the column
537/// type. User types mapped to PostgreSQL will work as well. If you need to
538/// extract the whole row into such a structure, you will need to disambiguate
539/// the call with the kRowTag.
540///
541/// @code
542/// auto string_set = generic_result.AsSetOf<std::string>();
543/// std::string s = string_set[0];
544///
545/// auto string_vec = generic_result.AsContainer<std::vector<std::string>>();
546///
547/// // Extract first column into the composite type
548/// auto foo_set = generic_result.AsSetOf<FooBar>();
549/// auto foo_vec = generic_result.AsContainer<std::vector<FooBar>>();
550///
551/// // Extract the whole row, disambiguation
552/// auto foo_set = generic_result.AsSetOf<FooBar>(kRowTag);
553///
554/// @endcode
555///
556///
557/// ----------
558///
559/// @htmlonly <div class="bottom-nav"> @endhtmlonly
560/// ⇦ @ref scripts/docs/en/userver/pg_types.md | @ref pg_errors ⇨
561/// @htmlonly </div> @endhtmlonly
562
563template <typename T, typename ExtractionTag>
564class TypedResultSet {
565public:
566 using size_type = ResultSet::size_type;
567 using difference_type = ResultSet::difference_type;
568 static constexpr size_type npos = ResultSet::npos;
569 static constexpr ExtractionTag kExtractTag{};
570
571 //@{
572 /** @name Row container concept */
573 using const_iterator = detail::ConstTypedRowIterator<T, ExtractionTag, detail::IteratorDirection::kForward>;
574 using const_reverse_iterator = detail::ConstTypedRowIterator<T, ExtractionTag, detail::IteratorDirection::kReverse>;
575
576 using value_type = T;
577 using pointer = const_iterator;
578
579// Forbidding assignments to operator[] result in debug, getting max
580// performance in release.
581#ifdef NDEBUG
582 using reference = value_type;
583#else
584 using reference = std::add_const_t<value_type>;
585#endif
586
587 //@}
588 explicit TypedResultSet(ResultSet result)
589 : result_{std::move(result)}
590 {}
591
592 /// Number of rows in the result set
593 size_type Size() const { return result_.Size(); }
594 bool IsEmpty() const { return Size() == 0; }
595 //@{
596 /** @name Container interface */
597 //@{
598 /** @name Row container interface */
599 //@{
600 /** @name Forward iteration */
601 const_iterator cbegin() const& { return const_iterator{result_.pimpl_, 0}; }
602 const_iterator begin() const& { return cbegin(); }
603 const_iterator cend() const& { return const_iterator{result_.pimpl_, Size()}; }
604 const_iterator end() const& { return cend(); }
605 const_iterator cbegin() const&& { ReportMisuse(); }
606 const_iterator begin() const&& { ReportMisuse(); }
607 const_iterator cend() const&& { ReportMisuse(); }
608 const_iterator end() const&& { ReportMisuse(); }
609 //@}
610 //@{
611 /** @name Reverse iteration */
612 const_reverse_iterator crbegin() const& { return const_reverse_iterator(result_.pimpl_, Size() - 1); }
613 const_reverse_iterator rbegin() const& { return crbegin(); }
614 const_reverse_iterator crend() const& { return const_reverse_iterator(result_.pimpl_, npos); }
615 const_reverse_iterator rend() const& { return crend(); }
616 const_reverse_iterator crbegin() const&& { ReportMisuse(); }
617 const_reverse_iterator rbegin() const&& { ReportMisuse(); }
618 const_reverse_iterator crend() const&& { ReportMisuse(); }
619 const_reverse_iterator rend() const&& { ReportMisuse(); }
620 //@}
621 /// @brief Access a row by index
622 /// @throws RowIndexOutOfBounds if index is out of bounds
623 // NOLINTNEXTLINE(readability-const-return-type)
624 reference operator[](size_type index) const& { return result_[index].template As<value_type>(kExtractTag); }
625 // NOLINTNEXTLINE(readability-const-return-type)
626 reference operator[](size_type) const&& { ReportMisuse(); }
627 //@}
628private:
629 [[noreturn]] static void ReportMisuse() {
630 static_assert(!sizeof(T), "keep the TypedResultSet before using, please");
631 }
632
633 ResultSet result_;
634};
635
636} // namespace storages::postgres
637
638USERVER_NAMESPACE_END