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>
79struct Enumerators {
80 static_assert(std::is_enum<Enum>(), "Type must be an enumeration");
81 static_assert(
82 requires { CppToUserPg<Enum>::enumerators; },
83 "CppToUserPg for an enumeration must contain a static `enumerators` member of `utils::TrivialBiMap` type or "
84 "`storages::postgres::io::detail::Enumerator[]`."
85 );
86 using Type = decltype(CppToUserPg<Enum>::enumerators);
87};
88
89template <typename Enum, typename = typename Enumerators<Enum>::Type>
90class EnumerationMap;
91
92template <typename Enum, typename T>
93class EnumerationMap {
94 using StringType = std::string_view;
95 using EnumType = Enum;
96 using MappingType = CppToUserPg<EnumType>;
97 using EnumeratorType = Enumerator<EnumType>;
98
99 struct EnumHash {
100 std::size_t operator()(EnumType v) const {
101 using underlying_type = std::underlying_type_t<EnumType>;
102 using hasher = std::hash<underlying_type>;
103 return hasher{}(static_cast<underlying_type>(v));
104 }
105 };
106
107 using EnumToString = std::unordered_map<EnumType, StringType, EnumHash>;
108 using StringToEnum = std::unordered_map<StringType, EnumType, utils::StringViewHash>;
109 using MapsPair = std::pair<EnumToString, StringToEnum>;
110
111 static MapsPair MapLiterals() {
112 MapsPair maps;
113 for (const auto& enumerator : enumerators) {
114 maps.first.insert(std::make_pair(enumerator.enumerator, enumerator.literal));
115 maps.second.insert(std::make_pair(enumerator.literal, enumerator.enumerator));
116 }
117 return maps;
118 }
119 static const MapsPair& GetMaps() {
120 static auto maps = MapLiterals();
121 return maps;
122 }
123 static const EnumToString& EnumeratorMap() { return GetMaps().first; }
124 static const StringToEnum& LiteralMap() { return GetMaps().second; }
125
126public:
127 static constexpr const auto& enumerators = MappingType::enumerators;
128 static constexpr std::size_t size = std::size(enumerators);
129 static EnumType GetEnumerator(StringType literal) {
130 static const auto& map = LiteralMap();
131 if (auto f = map.find(literal); f != map.end()) {
132 return f->second;
133 }
134 throw InvalidEnumerationLiteral{compiler::GetTypeName<EnumType>(), std::string{literal.data(), literal.size()}};
135 }
136 static StringType GetLiteral(EnumType enumerator) {
137 static const auto& map = EnumeratorMap();
138 if (auto f = map.find(enumerator); f != map.end()) {
139 return f->second;
140 }
141 throw InvalidEnumerationValue{enumerator};
142 }
143};
144
145template <typename Enum, typename Func>
146class EnumerationMap<Enum, const USERVER_NAMESPACE::utils::TrivialBiMap<Func>> {
147 using StringType = std::string_view;
148 using EnumType = Enum;
149 using MappingType = CppToUserPg<EnumType>;
150
151public:
152 static constexpr const auto& enumerators = MappingType::enumerators;
153 static constexpr std::size_t size = enumerators.size();
154 static constexpr EnumType GetEnumerator(StringType literal) {
155 auto enumerator = enumerators.TryFind(literal);
156 if (enumerator.has_value()) {
157 return *enumerator;
158 }
159 throw InvalidEnumerationLiteral{compiler::GetTypeName<EnumType>(), std::string{literal.data(), literal.size()}};
160 }
161 static constexpr StringType GetLiteral(EnumType enumerator) {
162 auto literal = enumerators.TryFind(enumerator);
163 if (literal.has_value()) {
164 return *literal;
165 }
166 throw InvalidEnumerationValue{enumerator};
167 }
168};
169
170template <typename Enum>
171class EnumerationMap<Enum, const Codegen> {
172 using EnumType = Enum;
173 using MappingType = CppToUserPg<EnumType>;
174
175public:
176 static EnumType GetEnumerator(std::string_view literal) { return Parse(literal, formats::parse::To<Enum>{}); }
177 static std::string GetLiteral(EnumType enumerator) { return ToString(enumerator); }
178};
179
180template <typename Enum>
181struct EnumParser : BufferParserBase<Enum> {
182 using BaseType = BufferParserBase<Enum>;
183 using EnumMap = EnumerationMap<Enum>;
184
185 using BaseType::BaseType;
186
187 void operator()(const FieldBuffer& buffer) {
188 std::string_view literal;
189 io::ReadBuffer(buffer, literal);
190 this->value = EnumMap::GetEnumerator(literal);
191 }
192};
193
194template <typename Enum>
195struct EnumFormatter : BufferFormatterBase<Enum> {
196 using BaseType = BufferFormatterBase<Enum>;
197 using EnumMap = EnumerationMap<Enum>;
198
199 using BaseType::BaseType;
200
201 template <typename Buffer>
202 void operator()(const UserTypes& types, Buffer& buffer) const {
203 auto literal = EnumMap::GetLiteral(this->value);
204 io::WriteBuffer(types, buffer, literal);
205 }
206};
207
208template <typename Enum>
209struct EnumConverter {
210 using PostgresType = std::string;
211 using UserType = Enum;
212
213 Enum operator()(const PostgresType& db_data) const { return Parse(db_data, formats::parse::To<Enum>()); }
214
215 PostgresType operator()(const Enum& user_data) const { return ToString(user_data); }
216};
217
218template <typename Enum>
219concept HasParse = requires(std::string_view sw) { Parse(sw, formats::parse::To<Enum>{}); };
220
221template <typename Enum>
222concept HasToString = requires(Enum e) { ToString(e); };
223
224} // namespace detail
225
226namespace traits {
227
228template <typename T>
229struct Input<T, std::enable_if_t<std::is_enum_v<T> && !detail::CustomParserDefined<T> && IsMappedToUserType<T>>> {
230 using type = io::detail::EnumParser<T>;
231};
232
233template <typename T>
234struct ParserBufferCategory<io::detail::EnumParser<T>>
235 : std::integral_constant<BufferCategory, BufferCategory::kPlainBuffer> {};
236
237template <typename T>
238struct Output<T, std::enable_if_t<std::is_enum_v<T> && !detail::CustomFormatterDefined<T> && IsMappedToUserType<T>>> {
239 using type = io::detail::EnumFormatter<T>;
240};
241
242// Specialization for enum parsing using ToString function
243// Allows C++ enums to be stored as PostgreSQL text when they have ToString support
244// but are not mapped to a specific PostgreSQL enum type via CppToUserPg
245template <typename Enum>
246struct Input<
247 Enum,
248 std::enable_if_t<
249 std::is_enum_v<Enum> && !detail::CustomParserDefined<Enum> && !IsMappedToUserType<Enum> &&
250 storages::postgres::io::detail::HasToString<Enum>>> {
251 using type = TransformParser<Enum, std::string, storages::postgres::io::detail::EnumConverter<Enum>>;
252};
253
254// Specialization for enum formatting using Parse functions
255// Allows C++ enums to be converted from PostgreSQL text when they have Parse support
256// but are not mapped to a specific PostgreSQL enum type via CppToUserPg
257template <typename Enum>
258struct Output<
259 Enum,
260 std::enable_if_t<
261 std::is_enum_v<Enum> && !detail::CustomFormatterDefined<Enum> && !IsMappedToUserType<Enum> &&
262 storages::postgres::io::detail::HasParse<Enum>>> {
263 using type = TransformFormatter<Enum, std::string, storages::postgres::io::detail::EnumConverter<Enum>>;
264};
265
266} // namespace traits
267
268} // namespace storages::postgres::io
269
270USERVER_NAMESPACE_END