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