userver: /data/code/userver/odbc/include/userver/storages/odbc/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/odbc/result_set.hpp
4/// @brief @copybrief storages::odbc::ResultSet
5
6#include <concepts>
7#include <iterator>
8#include <limits>
9#include <memory>
10#include <optional>
11#include <string>
12#include <string_view>
13#include <type_traits>
14#include <utility>
15
16#include <boost/pfr/core.hpp>
17#include <boost/pfr/traits.hpp>
18
19#include <userver/storages/odbc/exception.hpp>
20#include <userver/storages/odbc/odbc_fwd.hpp>
21#include <userver/storages/odbc/row.hpp>
22
23USERVER_NAMESPACE_BEGIN
24
25namespace storages::odbc {
26
27/// @cond
28namespace impl {
29
30struct OdbcResultMappingTag;
31
32template <typename T, std::size_t... Index>
33constexpr bool AreResultMembersMappable(std::index_sequence<Index...>) {
34 return sizeof...(Index) != 0 &&
35 ((!std::is_reference_v<boost::pfr::tuple_element_t<Index, T>> &&
36 kIsFieldAsType<std::remove_cv_t<boost::pfr::tuple_element_t<Index, T>>>) &&
37 ...);
38}
39
40template <typename T>
41constexpr bool DetectResultAggregate() {
42 using Value = std::remove_cv_t<T>;
43 if constexpr (std::is_class_v<Value> && std::is_aggregate_v<Value> && std::is_standard_layout_v<Value> &&
44 !std::is_union_v<Value> && io::traits::kAggregateHasNoBaseClass<Value> &&
45 boost::pfr::is_implicitly_reflectable_v<Value, OdbcResultMappingTag> && !kIsFieldAsType<Value> &&
46 !io::traits::kHasMappingDeclaration<Value>)
47 {
48 return AreResultMembersMappable<Value>(std::make_index_sequence<boost::pfr::tuple_size_v<Value>>{});
49 } else {
50 return false;
51 }
52}
53
54template <typename T>
55inline constexpr bool kIsResultAggregate = DetectResultAggregate<T>();
56
57template <typename T>
58inline constexpr bool kIsResultValue = kIsFieldAsType<std::remove_cv_t<T>> || kIsResultAggregate<T>;
59
60template <typename Container>
61concept ResultContainer =
62 std::default_initializable<Container> && !std::same_as<std::remove_cv_t<Container>, std::string> &&
63 requires { typename Container::value_type; } && kIsResultValue<typename Container::value_type> &&
64 requires(Container& container, typename Container::value_type value) {
65 container.insert(container.end(), std::move(value));
66 };
67
68} // namespace impl
69/// @endcond
70
71/// @brief Result set for ODBC query execution
72class ResultSet final {
73public:
74 using size_type = std::size_t;
75 using difference_type = std::ptrdiff_t;
76 static constexpr size_type npos = std::numeric_limits<size_type>::max();
77
78 //@{
79 /** @name Row container concept */
80
81 using value_type = Row;
82 using reference = value_type;
83 //@}
84
85 explicit ResultSet(std::shared_ptr<detail::ResultWrapper> pimpl)
86 : pimpl_{std::move(pimpl)}
87 {}
88
89 /// @brief Get the number of columns in the result set
90 size_type FieldCount() const;
91
92 size_type Size() const;
93
94 /// @brief Number of rows affected by a data-modifying statement.
95 /// Returns zero when the driver reports an unknown count.
96 size_type RowsAffected() const;
97
98 /// @brief Get a result column name by zero-based index.
99 std::string_view GetFieldName(size_type index) const;
100
101 /// @brief Check if the result set is empty
102 bool IsEmpty() const;
103
104 reference operator[](size_type index) const&;
105
106 /// Materializes every row into the container's value type. Scalar values
107 /// require exactly one result column; aggregate values are initialized in
108 /// declaration order and require an exact column count.
109 template <typename Container>
110 requires impl::ResultContainer<Container>
111 Container AsContainer() const;
112
113 /// Materializes the only result row, requiring exactly one row.
114 template <typename T>
115 requires impl::kIsResultValue<T>
116 T AsSingleRow() const;
117
118 /// Returns no value for zero rows, materializes one row, and rejects more
119 /// than one row. For optional-valued T the outer optional represents row
120 /// presence and the inner optional represents SQL NULL.
121 template <typename T>
122 requires impl::kIsResultValue<T>
123 std::optional<T> AsOptionalSingleRow() const;
124
125private:
126 template <typename T, std::size_t... Index>
127 T MapAggregate(size_type row_index, std::index_sequence<Index...>) const;
128
129 template <typename T>
130 T MapRow(size_type row_index) const;
131
132 std::shared_ptr<detail::ResultWrapper> pimpl_;
133};
134
135template <typename T, std::size_t... Index>
136T ResultSet::MapAggregate(size_type row_index, std::index_sequence<Index...>) const {
137 return T{
138 operator[](row_index)[Index]
139 .template As<std::remove_cvref_t<decltype(boost::pfr::get<Index>(std::declval<T&>()))>>()...
140 };
141}
142
143template <typename T>
144T ResultSet::MapRow(size_type row_index) const {
145 using Value = std::remove_cv_t<T>;
146 static_assert(impl::kIsResultValue<Value>, "Unsupported ODBC typed result value");
147
148 if constexpr (impl::kIsFieldAsType<Value>) {
149 if (FieldCount() != 1) {
150 throw ResultSetError("ODBC scalar result mapping requires exactly one column");
151 }
152 return operator[](row_index)[0].template As<Value>();
153 } else {
154 constexpr auto kFieldCount = boost::pfr::tuple_size_v<Value>;
155 if (FieldCount() != kFieldCount) {
156 throw ResultSetError("ODBC aggregate result mapping requires exactly one column per aggregate member");
157 }
158 return MapAggregate<Value>(row_index, std::make_index_sequence<kFieldCount>{});
159 }
160}
161
162template <typename Container>
163requires impl::ResultContainer<Container>
164Container ResultSet::AsContainer() const {
165 using Value = typename Container::value_type;
166 static_assert(impl::kIsResultValue<Value>, "Unsupported ODBC typed result container value");
167
168 Container result;
169 if constexpr (requires { result.reserve(Size()); }) {
170 result.reserve(Size());
171 }
172 auto output = std::inserter(result, result.end());
173 for (size_type index = 0; index < Size(); ++index) {
174 *output++ = MapRow<Value>(index);
175 }
176 return result;
177}
178
179template <typename T>
180requires impl::kIsResultValue<T>
181T ResultSet::AsSingleRow() const {
182 if (Size() != 1) {
183 throw ResultSetError("ODBC single-row result mapping requires exactly one row");
184 }
185 return MapRow<T>(0);
186}
187
188template <typename T>
189requires impl::kIsResultValue<T>
190std::optional<T> ResultSet::AsOptionalSingleRow() const {
191 if (Size() > 1) {
192 throw ResultSetError("ODBC optional single-row result mapping accepts at most one row");
193 }
194 if (IsEmpty()) {
195 return std::nullopt;
196 }
197 return std::optional<T>{MapRow<T>(0)};
198}
199
200} // namespace storages::odbc
201
202USERVER_NAMESPACE_END