userver: userver/storages/postgres/io/enum_types.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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