userver: userver/storages/postgres/io/composite_types.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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, 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) {
62 throw UnknownBufferCategory{
63 static_cast<Oid>(field_type), compiler::GetTypeName<U>(), compiler::GetTypeName<T>()};
64 }
65 buffer.ReadRaw(val, categories, elem_category);
66 }
67
68 template <typename Tuple, std::size_t... Indexes>
69 void
70 ReadTuple(FieldBuffer& buffer, const TypeBufferCategory& categories, Tuple&& tuple, std::index_sequence<Indexes...>)
71 const {
72 (ReadField(buffer, categories, std::get<Indexes>(std::forward<Tuple>(tuple))), ...);
73 }
74};
75
76template <typename T>
77struct CompositeBinaryFormatter : BufferFormatterBase<T> {
78 using BaseType = BufferFormatterBase<T>;
79 using PgMapping = CppToPg<T>;
80 using RowType = io::RowType<T>;
81 using IndexSequence = typename RowType::IndexSequence;
82 static constexpr std::size_t size = RowType::size;
83
84 using BaseType::BaseType;
85
86 template <typename Buffer>
87 void operator()(const UserTypes& types, Buffer& buffer) const {
88 const auto oid = PgMapping::GetOid(types);
89 if (!oid) {
90 auto msg = fmt::format(
91 "Type '{}' was not created in database and because of that the '{}' could not be serialized. Forgot a "
92 "migration or rolled it after the service started?",
93 kPgUserTypeName<T>.ToString(),
94 compiler::GetTypeName<T>()
95 );
96 throw UserTypeError(std::move(msg));
97 }
98
99 const auto& type_desc = types.GetCompositeDescription(oid);
100 if (type_desc.Size() != size) {
101 throw CompositeSizeMismatch{type_desc.Size(), size, compiler::GetTypeName<T>()};
102 }
103 // Number of fields
104 io::WriteBuffer(types, buffer, static_cast<Integer>(size));
105 WriteTuple(types, type_desc, buffer, RowType::GetTuple(this->value), IndexSequence{});
106 }
107
108private:
109 template <typename Buffer, typename U>
110 void WriteField(
111 const UserTypes& types,
112 const CompositeTypeDescription& type_desc,
113 std::size_t index,
114 Buffer& buffer,
115 const U& val
116 ) const {
117 auto field_type = CppToPg<U>::GetOid(types);
118 const auto& field_desc = type_desc[index];
119 if (field_type != field_desc.type) {
120 if (io::MappedToSameType(
121 static_cast<PredefinedOids>(field_type),
122 static_cast<PredefinedOids>(types.FindDomainBaseOid(field_desc.type))
123 )) {
124 field_type = field_desc.type;
125 } else {
126 throw CompositeMemberTypeMismatch(
127 PgMapping::postgres_name.schema,
128 PgMapping::postgres_name.name,
129 field_desc.name,
130 field_desc.type,
131 field_type
132 );
133 }
134 }
135 io::WriteBuffer(types, buffer, static_cast<Integer>(field_type));
136 io::WriteRawBinary(types, buffer, val, field_type);
137 }
138 template <typename Buffer, typename Tuple, std::size_t... Indexes>
139 void
140 WriteTuple(const UserTypes& types, const CompositeTypeDescription& type_desc, Buffer& buffer, Tuple&& tuple, std::index_sequence<Indexes...>)
141 const {
142 (WriteField(types, type_desc, Indexes, buffer, std::get<Indexes>(std::forward<Tuple>(tuple))), ...);
143 }
144};
145
146} // namespace detail
147
148namespace traits {
149
150namespace detail {
151
152template <typename Tuple>
153struct AssertTupleHasParsers;
154
155template <typename... Members>
156struct AssertTupleHasParsers<std::tuple<Members...>> : std::true_type {
157 static_assert(
158 (HasParser<std::decay_t<Members>>::value && ...),
159 "No parser for member. Probably you forgot to include "
160 "file with parser or to define your own. Please see page "
161 "`uPg: Supported data types` for more information"
162 );
163};
164
165template <typename Tuple>
166struct AssertTupleHasFormatters;
167
168template <typename... Members>
169struct AssertTupleHasFormatters<std::tuple<Members...>> : std::true_type {
170 static_assert(
171 (HasFormatter<std::decay_t<Members>>::value && ...),
172 "No formatter for member. Probably you forgot to "
173 "include file with formatter or to define your own. Please see page "
174 "`uPg: Supported data types` for more information"
175 );
176};
177
178template <typename T>
179constexpr bool AssertHasCompositeParsers() {
180 io::traits::AssertIsValidRowType<T>();
181 return AssertTupleHasParsers<typename io::RowType<T>::TupleType>::value;
182}
183
184template <typename T>
185constexpr bool AssertHasCompositeFormatters() {
186 io::traits::AssertIsValidRowType<T>();
187 return AssertTupleHasFormatters<typename io::RowType<T>::TupleType>::value;
188}
189
190} // namespace detail
191
192template <typename T>
194 static_assert(detail::AssertHasCompositeParsers<T>());
196};
197
198template <typename T>
200 static_assert(detail::AssertHasCompositeFormatters<T>());
202};
203
204template <typename T>
205struct ParserBufferCategory<io::detail::CompositeBinaryParser<T>>
207
208} // namespace traits
209} // namespace storages::postgres::io
210
211USERVER_NAMESPACE_END