userver: userver/storages/postgres/io/enum_types.hpp Source File
Loading...
Searching...
No Matches
enum_types.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/io/enum_types.hpp
4/// @brief Enum I/O support
5/// @ingroup userver_postgres_parse_and_format
6
7#include <string_view>
8#include <unordered_map>
9
10#include <userver/storages/postgres/io/buffer_io.hpp>
11#include <userver/storages/postgres/io/buffer_io_base.hpp>
12#include <userver/storages/postgres/io/pg_types.hpp>
13#include <userver/storages/postgres/io/type_mapping.hpp>
14#include <userver/storages/postgres/io/user_types.hpp>
15#include <userver/utils/trivial_map_fwd.hpp>
16
17#include <userver/storages/postgres/detail/string_hash.hpp>
18
19USERVER_NAMESPACE_BEGIN
20
21namespace storages::postgres::io {
22
23// clang-format off
24
25/// @page pg_enum uPg: Mapping a C++ enum to PostgreSQL enum type.
26///
27/// A C++ enumeration can be mapped to a PostgreSQL enum type by providing a
28/// list of PostgreSQL literals mapped to the enumeration values.
29///
30/// For example, if we have a C++ enumeration declared as follows:
31/// @snippet storages/postgres/tests/enums_pgtest.cpp C++ enum type
32/// and a PostgreSQL enum type:
33/// @code
34/// create type __pgtest.rainbow as enum (
35/// 'red', 'orange', 'yellow', 'green', 'cyan'
36/// )
37/// @endcode
38/// all we need to do to declare the mapping between the C++ type and PosgtreSQL
39/// enumeration is provide a specialization of CppToUserPg template.
40/// The difference from mapping a C++ type to other user PosgreSQL types, for an
41/// enumeration we need to provide a list of enumerators with corresponding
42/// literals.
43///
44/// @warning The type mapping specialization **must** be accessible at the
45/// points where parsing/formatting of the C++ type is instantiated. The
46/// header where the C++ type is declared is an appropriate place to do it.
47///
48/// @snippet storages/postgres/tests/enums_pgtest.cpp C++ to Pg mapping
49///
50/// The specialization of CppToUserPg derives from EnumMappingBase for it to
51/// provide type aliases for EnumeratorList and EnumType. EnumType is an alias
52/// to the enumeration type for convenience of declaring pairs, as the
53/// enumeration can have a long qualified name.
54///
55/// There is an alternative way to specialize the CppToUserPg template using
56/// TrivialBiMap. This way is much more efficient, so it is preferable to use
57/// it. It also becomes possible to reuse an existing TrivialBiMap.
58///
59/// @snippet storages/postgres/tests/enums_pgtest.cpp C++ to Pg TrivialBiMap mapping
60///
61/// ----------
62///
63/// @htmlonly <div class="bottom-nav"> @endhtmlonly
64/// ⇦ @ref pg_composite_types | @ref pg_range_types ⇨
65/// @htmlonly </div> @endhtmlonly
66
67// clang-format on
68
69namespace detail {
70
71/// Enumerator literal value holder
72template <typename Enum>
73struct Enumerator {
74 Enum enumerator;
75 std::string_view literal;
76
77 constexpr Enumerator(Enum en, std::string_view lit)
78 : enumerator{en}, literal{lit} {}
79};
80
81} // namespace detail
82
83/// Base template for providing type aliases for defining enumeration
84/// mapping.
85template <typename Enum>
87 static_assert(std::is_enum<Enum>(), "Type must be an enumeration");
88 /// @brief Type alias for the enumeration.
89 ///
90 /// As the mapping must be specialized in `storages::postgres::io` namespace,
91 /// the enumerator value can be quite a long name. This type alias is a
92 /// shortcut to the enumeration type.
93 using EnumType = Enum;
94 /// Type alias for enumerator literal value holder
95 using Enumerator = detail::Enumerator<Enum>;
96 /// @brief Type alias for enumerator-literal mapping.
97 ///
98 /// See @ref pg_enum
99 using EnumeratorList = const std::initializer_list<Enumerator>;
100};
101
102namespace detail {
103
104template <typename Enum, typename Enable = USERVER_NAMESPACE::utils::void_t<>>
105struct AreEnumeratorsDefined : std::false_type {};
106
107template <typename Enum>
108struct AreEnumeratorsDefined<
109 Enum,
110 USERVER_NAMESPACE::utils::void_t<decltype(CppToUserPg<Enum>::enumerators)*>>
111 : std::true_type {};
112
113template <typename Enum>
114struct Enumerators {
115 static_assert(std::is_enum<Enum>(), "Type must be an enumeration");
116 static_assert(AreEnumeratorsDefined<Enum>(),
117 "CppToUserPg for an enumeration must contain a static "
118 "`enumerators` member of `utils::TrivialBiMap` type or "
119 "`storages::postgres::io::detail::Enumerator[]`");
120 using Type = decltype(CppToUserPg<Enum>::enumerators);
121};
122
123template <typename Enum, typename = typename Enumerators<Enum>::Type>
124class EnumerationMap;
125
126template <typename Enum, typename T>
127class EnumerationMap {
128 using StringType = std::string_view;
129 using EnumType = Enum;
130 using MappingType = CppToUserPg<EnumType>;
131 using EnumeratorType = Enumerator<EnumType>;
132
133 struct EnumHash {
134 std::size_t operator()(EnumType v) const {
135 using underlying_type = std::underlying_type_t<EnumType>;
136 using hasher = std::hash<underlying_type>;
137 return hasher{}(static_cast<underlying_type>(v));
138 }
139 };
140
141 using EnumToString = std::unordered_map<EnumType, StringType, EnumHash>;
142 using StringToEnum =
143 std::unordered_map<StringType, EnumType, utils::StringViewHash>;
144 using MapsPair = std::pair<EnumToString, StringToEnum>;
145
146 static MapsPair MapLiterals() {
147 MapsPair maps;
148 for (const auto& enumerator : enumerators) {
149 maps.first.insert(
150 std::make_pair(enumerator.enumerator, enumerator.literal));
151 maps.second.insert(
152 std::make_pair(enumerator.literal, enumerator.enumerator));
153 }
154 return maps;
155 }
156 static const MapsPair& GetMaps() {
157 static auto maps_ = MapLiterals();
158 return maps_;
159 }
160 static const EnumToString& EnumeratorMap() { return GetMaps().first; }
161 static const StringToEnum& LiteralMap() { return GetMaps().second; }
162
163 public:
164 static constexpr const auto& enumerators = MappingType::enumerators;
165 static constexpr std::size_t size = std::size(enumerators);
166 static EnumType GetEnumerator(StringType literal) {
167 static const auto& map = LiteralMap();
168 if (auto f = map.find(literal); f != map.end()) {
169 return f->second;
170 }
171 throw InvalidEnumerationLiteral{
172 compiler::GetTypeName<EnumType>(),
173 std::string{literal.data(), literal.size()}};
174 }
175 static StringType GetLiteral(EnumType enumerator) {
176 static const auto& map = EnumeratorMap();
177 if (auto f = map.find(enumerator); f != map.end()) {
178 return f->second;
179 }
180 throw InvalidEnumerationValue{enumerator};
181 }
182};
183
184template <typename Enum, typename Func>
185class EnumerationMap<Enum, const USERVER_NAMESPACE::utils::TrivialBiMap<Func>> {
186 using StringType = std::string_view;
187 using EnumType = Enum;
188 using MappingType = CppToUserPg<EnumType>;
189
190 public:
191 static constexpr const auto& enumerators = MappingType::enumerators;
192 static constexpr std::size_t size = enumerators.size();
193 static constexpr EnumType GetEnumerator(StringType literal) {
194 auto enumerator = enumerators.TryFind(literal);
195 if (enumerator.has_value()) return *enumerator;
196 throw InvalidEnumerationLiteral{
197 compiler::GetTypeName<EnumType>(),
198 std::string{literal.data(), literal.size()}};
199 }
200 static constexpr StringType GetLiteral(EnumType enumerator) {
201 auto literal = enumerators.TryFind(enumerator);
202 if (literal.has_value()) return *literal;
203 throw InvalidEnumerationValue{enumerator};
204 }
205};
206
207template <typename Enum>
208struct EnumParser : BufferParserBase<Enum> {
209 using BaseType = BufferParserBase<Enum>;
210 using EnumMap = EnumerationMap<Enum>;
211
212 using BaseType::BaseType;
213
214 void operator()(const FieldBuffer& buffer) {
215 std::string_view literal;
216 io::ReadBuffer(buffer, literal);
217 this->value = EnumMap::GetEnumerator(literal);
218 }
219};
220
221template <typename Enum>
222struct EnumFormatter : BufferFormatterBase<Enum> {
223 using BaseType = BufferFormatterBase<Enum>;
224 using EnumMap = EnumerationMap<Enum>;
225
226 using BaseType::BaseType;
227
228 template <typename Buffer>
229 void operator()(const UserTypes& types, Buffer& buffer) const {
230 auto literal = EnumMap::GetLiteral(this->value);
231 io::WriteBuffer(types, buffer, literal);
232 }
233};
234
235} // namespace detail
236
237namespace traits {
238
239template <typename T>
240struct Input<T,
242 std::is_enum<T>() && IsMappedToUserType<T>()>> {
243 using type = io::detail::EnumParser<T>;
244};
245
246template <typename T>
247struct ParserBufferCategory<io::detail::EnumParser<T>>
249
250template <typename T>
251struct Output<T,
253 std::is_enum<T>() && IsMappedToUserType<T>()>> {
254 using type = io::detail::EnumFormatter<T>;
255};
256
257} // namespace traits
258
259} // namespace storages::postgres::io
260
261USERVER_NAMESPACE_END