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/transform_io.hpp>
14#include <userver/storages/postgres/io/type_mapping.hpp>
15#include <userver/storages/postgres/io/user_types.hpp>
16#include <userver/utils/trivial_map_fwd.hpp>
17
18#include <userver/storages/postgres/detail/string_hash.hpp>
19
20USERVER_NAMESPACE_BEGIN
21
22namespace storages::postgres::io {
23
24namespace detail {
25
26/// Enumerator literal value holder
27template <typename Enum>
28struct Enumerator {
29 Enum enumerator;
30 std::string_view literal;
31
32 constexpr Enumerator(Enum en, std::string_view lit)
33 : enumerator{en},
34 literal{lit}
35 {}
36};
37
38} // namespace detail
39
40/// Optional base template for providing type aliases for defining enumeration
41/// mapping.
42template <typename Enum>
44 static_assert(std::is_enum<Enum>(), "Type must be an enumeration");
45
46 /// @brief Type alias for the enumeration.
47 ///
48 /// As the mapping must be specialized in `storages::postgres::io` namespace,
49 /// the enumerator value can be quite a long name. This type alias is a
50 /// shortcut to the enumeration type.
51 using EnumType = Enum;
52
53 /// Type alias for enumerator literal value holder
54 using Enumerator = detail::Enumerator<Enum>;
55
56 /// @brief Type alias for enumerator-literal mapping.
57 ///
58 /// See @ref pg_enum
59 using EnumeratorList = const std::initializer_list<Enumerator>;
60};
61
62/// Helper for enumerators that use `Parse` and `ToString` functions for
63/// conversions, mostly for autogenerated enums from chaotic.
64///
65/// A generated enum:
66/// @snippet storages/postgres/tests/enums_pgtest.cpp autogenerated enum type postgres
67/// can be registered in the following way
68/// @snippet storages/postgres/tests/enums_pgtest.cpp autogenerated enum registration postgres
69///
70/// After that the enum is mapped to the PostgreSQL type that can be created in the following way:
71/// @snippet storages/postgres/tests/enums_pgtest.cpp User enum type postgres
72/// Use it as usual:
73/// @snippet storages/postgres/tests/enums_pgtest.cpp autogenerated enum usage postgres
74struct Codegen {};
75
76namespace detail {
77
78template <typename Enum, typename Enable = USERVER_NAMESPACE::utils::void_t<>>
79struct AreEnumeratorsDefined : std::false_type {};
80
81template <typename Enum>
82struct AreEnumeratorsDefined<Enum, USERVER_NAMESPACE::utils::void_t<decltype(CppToUserPg<Enum>::enumerators)*>>
83 : std::true_type {};
84
85template <typename Enum>
86struct Enumerators {
87 static_assert(std::is_enum<Enum>(), "Type must be an enumeration");
88 static_assert(
89 AreEnumeratorsDefined<Enum>(),
90 "CppToUserPg for an enumeration must contain a static "
91 "`enumerators` member of `utils::TrivialBiMap` type or "
92 "`storages::postgres::io::detail::Enumerator[]`"
93 );
94 using Type = decltype(CppToUserPg<Enum>::enumerators);
95};
96
97template <typename Enum, typename = typename Enumerators<Enum>::Type>
98class EnumerationMap;
99
100template <typename Enum, typename T>
101class EnumerationMap {
102 using StringType = std::string_view;
103 using EnumType = Enum;
104 using MappingType = CppToUserPg<EnumType>;
105 using EnumeratorType = Enumerator<EnumType>;
106
107 struct EnumHash {
108 std::size_t operator()(EnumType v) const {
109 using underlying_type = std::underlying_type_t<EnumType>;
110 using hasher = std::hash<underlying_type>;
111 return hasher{}(static_cast<underlying_type>(v));
112 }
113 };
114
115 using EnumToString = std::unordered_map<EnumType, StringType, EnumHash>;
116 using StringToEnum = std::unordered_map<StringType, EnumType, utils::StringViewHash>;
117 using MapsPair = std::pair<EnumToString, StringToEnum>;
118
119 static MapsPair MapLiterals() {
120 MapsPair maps;
121 for (const auto& enumerator : enumerators) {
122 maps.first.insert(std::make_pair(enumerator.enumerator, enumerator.literal));
123 maps.second.insert(std::make_pair(enumerator.literal, enumerator.enumerator));
124 }
125 return maps;
126 }
127 static const MapsPair& GetMaps() {
128 static auto maps = MapLiterals();
129 return maps;
130 }
131 static const EnumToString& EnumeratorMap() { return GetMaps().first; }
132 static const StringToEnum& LiteralMap() { return GetMaps().second; }
133
134public:
135 static constexpr const auto& enumerators = MappingType::enumerators;
136 static constexpr std::size_t size = std::size(enumerators);
137 static EnumType GetEnumerator(StringType literal) {
138 static const auto& map = LiteralMap();
139 if (auto f = map.find(literal); f != map.end()) {
140 return f->second;
141 }
142 throw InvalidEnumerationLiteral{compiler::GetTypeName<EnumType>(), std::string{literal.data(), literal.size()}};
143 }
144 static StringType GetLiteral(EnumType enumerator) {
145 static const auto& map = EnumeratorMap();
146 if (auto f = map.find(enumerator); f != map.end()) {
147 return f->second;
148 }
149 throw InvalidEnumerationValue{enumerator};
150 }
151};
152
153template <typename Enum, typename Func>
154class EnumerationMap<Enum, const USERVER_NAMESPACE::utils::TrivialBiMap<Func>> {
155 using StringType = std::string_view;
156 using EnumType = Enum;
157 using MappingType = CppToUserPg<EnumType>;
158
159public:
160 static constexpr const auto& enumerators = MappingType::enumerators;
161 static constexpr std::size_t size = enumerators.size();
162 static constexpr EnumType GetEnumerator(StringType literal) {
163 auto enumerator = enumerators.TryFind(literal);
164 if (enumerator.has_value()) {
165 return *enumerator;
166 }
167 throw InvalidEnumerationLiteral{compiler::GetTypeName<EnumType>(), std::string{literal.data(), literal.size()}};
168 }
169 static constexpr StringType GetLiteral(EnumType enumerator) {
170 auto literal = enumerators.TryFind(enumerator);
171 if (literal.has_value()) {
172 return *literal;
173 }
174 throw InvalidEnumerationValue{enumerator};
175 }
176};
177
178template <typename Enum>
179class EnumerationMap<Enum, const Codegen> {
180 using EnumType = Enum;
181 using MappingType = CppToUserPg<EnumType>;
182
183public:
184 static EnumType GetEnumerator(std::string_view literal) { return Parse(literal, formats::parse::To<Enum>{}); }
185 static std::string GetLiteral(EnumType enumerator) { return ToString(enumerator); }
186};
187
188template <typename Enum>
189struct EnumParser : BufferParserBase<Enum> {
190 using BaseType = BufferParserBase<Enum>;
191 using EnumMap = EnumerationMap<Enum>;
192
193 using BaseType::BaseType;
194
195 void operator()(const FieldBuffer& buffer) {
196 std::string_view literal;
197 io::ReadBuffer(buffer, literal);
198 this->value = EnumMap::GetEnumerator(literal);
199 }
200};
201
202template <typename Enum>
203struct EnumFormatter : BufferFormatterBase<Enum> {
204 using BaseType = BufferFormatterBase<Enum>;
205 using EnumMap = EnumerationMap<Enum>;
206
207 using BaseType::BaseType;
208
209 template <typename Buffer>
210 void operator()(const UserTypes& types, Buffer& buffer) const {
211 auto literal = EnumMap::GetLiteral(this->value);
212 io::WriteBuffer(types, buffer, literal);
213 }
214};
215
216template <typename Enum>
217struct EnumConverter {
218 using PostgresType = std::string;
219 using UserType = Enum;
220
221 Enum operator()(const PostgresType& db_data) const { return Parse(db_data, formats::parse::To<Enum>()); }
222
223 PostgresType operator()(const Enum& user_data) const { return ToString(user_data); }
224};
225
226template <typename>
227auto HasParseImpl(...) -> std::false_type;
228
229template <typename Enum>
230auto HasParseImpl(int
231) -> decltype(Parse(std::declval<std::string_view>(), std::declval<formats::parse::To<Enum>>()), std::true_type{});
232
233template <typename Enum>
234struct HasParse : decltype(storages::postgres::io::detail::HasParseImpl<Enum>(0)) {};
235
236template <typename>
237auto HasToStringImpl(...) -> std::false_type;
238
239template <typename Enum>
240auto HasToStringImpl(int) -> decltype(ToString(std::declval<Enum>()), std::true_type{});
241
242template <typename Enum>
243struct HasToString : decltype(storages::postgres::io::detail::HasToStringImpl<Enum>(0)) {};
244
245} // namespace detail
246
247namespace traits {
248
249template <typename T>
250struct Input<T, std::enable_if_t<std::is_enum<T>() && !detail::kCustomParserDefined<T> && IsMappedToUserType<T>()>> {
251 using type = io::detail::EnumParser<T>;
252};
253
254template <typename T>
255struct ParserBufferCategory<io::detail::EnumParser<T>>
256 : std::integral_constant<BufferCategory, BufferCategory::kPlainBuffer> {};
257
258template <typename T>
259struct
260 Output<T, std::enable_if_t<std::is_enum<T>() && !detail::kCustomFormatterDefined<T> && IsMappedToUserType<T>()>> {
261 using type = io::detail::EnumFormatter<T>;
262};
263
264// Specialization for enum parsing using ToString function
265// Allows C++ enums to be stored as PostgreSQL text when they have ToString support
266// but are not mapped to a specific PostgreSQL enum type via CppToUserPg
267template <typename Enum>
268struct Input<
269 Enum,
270 std::enable_if_t<
271 std::is_enum<Enum>() && !detail::kCustomParserDefined<Enum> && !IsMappedToUserType<Enum>() &&
272 storages::postgres::io::detail::HasToString<Enum>::value>> {
273 using type = TransformParser<Enum, std::string, storages::postgres::io::detail::EnumConverter<Enum>>;
274};
275
276// Specialization for enum formatting using Parse functions
277// Allows C++ enums to be converted from PostgreSQL text when they have Parse support
278// but are not mapped to a specific PostgreSQL enum type via CppToUserPg
279template <typename Enum>
280struct Output<
281 Enum,
282 std::enable_if_t<
283 std::is_enum<Enum>() && !detail::kCustomFormatterDefined<Enum> && !IsMappedToUserType<Enum>() &&
284 storages::postgres::io::detail::HasParse<Enum>::value>> {
285 using type = TransformFormatter<Enum, std::string, storages::postgres::io::detail::EnumConverter<Enum>>;
286};
287
288} // namespace traits
289
290} // namespace storages::postgres::io
291
292USERVER_NAMESPACE_END