userver: userver/storages/postgres/io/composite_types.hpp Source File
Loading...
Searching...
No Matches
composite_types.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/io/composite_types.hpp
4/// @brief Composite types I/O support
5/// @ingroup userver_postgres_parse_and_format
6
7#include <fmt/format.h>
8#include <boost/pfr/core.hpp>
9#include <utility>
10
11#include <userver/compiler/demangle.hpp>
12
13#include <userver/storages/postgres/exceptions.hpp>
14#include <userver/storages/postgres/io/buffer_io_base.hpp>
15#include <userver/storages/postgres/io/field_buffer.hpp>
16#include <userver/storages/postgres/io/row_types.hpp>
17#include <userver/storages/postgres/io/traits.hpp>
18#include <userver/storages/postgres/io/type_mapping.hpp>
19#include <userver/storages/postgres/io/type_traits.hpp>
20#include <userver/storages/postgres/io/user_types.hpp>
21
22USERVER_NAMESPACE_BEGIN
23
24namespace storages::postgres::io {
25
26namespace detail {
27
28void InitRecordParser();
29
30template <typename T>
31struct CompositeBinaryParser : BufferParserBase<T> {
32 using BaseType = BufferParserBase<T>;
33 using RowType = io::RowType<T>;
34 using IndexSequence = typename RowType::IndexSequence;
35
36 using BaseType::BaseType;
37
38 void operator()(FieldBuffer buffer, const TypeBufferCategory& categories) {
39 if constexpr (!traits::kIsMappedToUserType<T>) {
40 InitRecordParser();
41 }
42
43 Integer field_count{0};
44 buffer.Read(field_count, BufferCategory::kPlainBuffer);
45
46 if (field_count != RowType::size) {
47 throw CompositeSizeMismatch(field_count, RowType::size, compiler::GetTypeName<T>());
48 }
49
50 ReadTuple(buffer, categories, RowType::GetTuple(this->value), IndexSequence{});
51 }
52
53private:
54 static constexpr std::size_t int_size = sizeof(Integer);
55
56 template <typename U>
57 void ReadField(FieldBuffer& buffer, const TypeBufferCategory& categories, std::size_t field_index, U& val) const {
58 Integer field_type = 0;
59 buffer.Read(field_type, BufferCategory::kPlainBuffer);
60 auto elem_category = GetTypeBufferCategory(categories, field_type);
61 if (elem_category == BufferCategory::kNoParser) {
63 static_cast<Oid>(field_type),
64 compiler::GetTypeName<U>(),
65 compiler::GetTypeName<T>()
66 };
67 }
68 try {
69 buffer.ReadRaw(val, categories, elem_category);
70 } catch (ResultSetError& error) {
71 error.AddMsgSuffix(fmt::format(
72 ",\n\twhile reading from database to C++ type '{}' (field #{} of a composite C++ type '{}')",
73 compiler::GetTypeName<U>(),
74 field_index,
75 compiler::GetTypeName<T>()
76 ));
77 throw;
78 }
79 }
80
81 template <typename Tuple, std::size_t... Indexes>
82 void
83 ReadTuple(FieldBuffer& buffer, const TypeBufferCategory& categories, Tuple&& tuple, std::index_sequence<Indexes...>)
84 const {
85 (ReadField(buffer, categories, Indexes, std::get<Indexes>(std::forward<Tuple>(tuple))), ...);
86 }
87};
88
89template <typename T>
90struct CompositeBinaryFormatter : BufferFormatterBase<T> {
91 using BaseType = BufferFormatterBase<T>;
92 using PgMapping = CppToPg<T>;
93 using RowType = io::RowType<T>;
94 using IndexSequence = typename RowType::IndexSequence;
95 static constexpr std::size_t size = RowType::size;
96
97 using BaseType::BaseType;
98
99 template <typename Buffer>
100 void operator()(const UserTypes& types, Buffer& buffer) const {
101 const auto oid = PgMapping::GetOid(types);
102 if (!oid) {
103 auto msg = fmt::format(
104 "Type '{}' was not created in database and because of that the '{}' could not be serialized. Forgot a "
105 "migration or rolled it after the service started?",
106 kPgUserTypeName<T>.ToString(),
107 compiler::GetTypeName<T>()
108 );
109 throw UserTypeError(std::move(msg));
110 }
111
112 const auto& type_desc = types.GetCompositeDescription(oid);
113 if (type_desc.Size() != size) {
114 throw CompositeSizeMismatch{type_desc.Size(), size, compiler::GetTypeName<T>()};
115 }
116 // Number of fields
117 io::WriteBuffer(types, buffer, static_cast<Integer>(size));
118 WriteTuple(types, type_desc, buffer, RowType::GetTuple(this->value), IndexSequence{});
119 }
120
121private:
122 template <typename Buffer, typename U>
123 void WriteField(
124 const UserTypes& types,
125 const CompositeTypeDescription& type_desc,
126 std::size_t index,
127 Buffer& buffer,
128 const U& val
129 ) const {
130 auto field_type = CppToPg<U>::GetOid(types);
131 const auto& field_desc = type_desc[index];
132 if (field_type != field_desc.type) {
134 static_cast<PredefinedOids>(field_type),
135 static_cast<PredefinedOids>(types.FindDomainBaseOid(field_desc.type))
136 ))
137 {
138 field_type = field_desc.type;
139 } else {
141 PgMapping::postgres_name.schema,
142 PgMapping::postgres_name.name,
143 field_desc.name,
144 field_desc.type,
145 field_type
146 );
147 }
148 }
149 io::WriteBuffer(types, buffer, static_cast<Integer>(field_type));
150 io::WriteRawBinary(types, buffer, val, field_type);
151 }
152 template <typename Buffer, typename Tuple, std::size_t... Indexes>
153 void
154 WriteTuple(const UserTypes& types, const CompositeTypeDescription& type_desc, Buffer& buffer, Tuple&& tuple, std::index_sequence<Indexes...>)
155 const {
156 (WriteField(types, type_desc, Indexes, buffer, std::get<Indexes>(std::forward<Tuple>(tuple))), ...);
157 }
158};
159
160} // namespace detail
161
162namespace traits {
163
164namespace detail {
165
166template <typename Tuple>
167struct AssertTupleHasParsers;
168
169template <typename... Members>
170struct AssertTupleHasParsers<std::tuple<Members...>> : std::true_type {
171 static_assert(
172 (HasParser<std::decay_t<Members>>::value && ...),
173 "No parser for member. Probably you forgot to include "
174 "file with parser or to define your own. Please see page "
175 "`uPg: Supported data types` for more information"
176 );
177};
178
179template <typename Tuple>
180struct AssertTupleHasFormatters;
181
182template <typename... Members>
183struct AssertTupleHasFormatters<std::tuple<Members...>> : std::true_type {
184 static_assert(
185 (HasFormatter<std::decay_t<Members>>::value && ...),
186 "No formatter for member. Probably you forgot to "
187 "include file with formatter or to define your own. Please see page "
188 "`uPg: Supported data types` for more information"
189 );
190};
191
192template <typename T>
193constexpr bool AssertHasCompositeParsers() {
194 io::traits::AssertIsValidRowType<T>();
195 return AssertTupleHasParsers<typename io::RowType<T>::TupleType>::value;
196}
197
198template <typename T>
199constexpr bool AssertHasCompositeFormatters() {
200 io::traits::AssertIsValidRowType<T>();
201 return AssertTupleHasFormatters<typename io::RowType<T>::TupleType>::value;
202}
203
204} // namespace detail
205
206template <typename T>
207struct Input<T, std::enable_if_t<!detail::kCustomParserDefined<T> && kIsRowType<T>>> {
208 static_assert(detail::AssertHasCompositeParsers<T>());
209 using type = io::detail::CompositeBinaryParser<T>;
210};
211
212template <typename T>
213struct Output<T, std::enable_if_t<!detail::kCustomFormatterDefined<T> && kIsMappedToUserType<T> && kIsRowType<T>>> {
214 static_assert(detail::AssertHasCompositeFormatters<T>());
215 using type = io::detail::CompositeBinaryFormatter<T>;
216};
217
218template <typename T>
219struct ParserBufferCategory<io::detail::CompositeBinaryParser<T>>
220 : std::integral_constant<BufferCategory, BufferCategory::kCompositeBuffer> {};
221
222} // namespace traits
223} // namespace storages::postgres::io
224
225USERVER_NAMESPACE_END