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