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
25// clang-format wraps snippet lines
26// clang-format off
27/// @page pg_composite_types uPg: Composite user types
28///
29/// The driver supports user-defined PostgreSQL composite types. The C++
30/// counterpart type must satisfy the same requirements as for the row types,
31/// (@ref pg_user_row_types) and must provide a specialization of CppToUserPg
32/// template (@ref pg_user_types).
33///
34/// Parsing a composite structure from PostgreSQL buffer will throw an error if
35/// the number of fields in the postgres data is different from the number of
36/// data members in target C++ type. This is the only sanity control for the
37/// composite types. The driver doesn't check the data type oids, it's user's
38/// responsibility to provide structures with compatible data members.
39///
40/// @par Examples from tests
41///
42/// @snippet storages/postgres/tests/composite_types_pgtest.cpp User type declaration
43///
44/// @warning The type mapping specialization **must** be accessible at the
45/// points where parsing/formatting of the C++ type is instantiated. The
46/// header where the C++ type is declared is an appropriate place to do it.
47///
48/// @snippet storages/postgres/tests/composite_types_pgtest.cpp User type mapping
49///
50///
51/// ----------
52///
53/// @htmlonly <div class="bottom-nav"> @endhtmlonly
54/// ⇦ @ref pg_user_types | @ref pg_enum ⇨
55/// @htmlonly </div> @endhtmlonly
56
57// clang-format on
58
59namespace detail {
60
61void InitRecordParser();
62
63template <typename T>
64struct CompositeBinaryParser : BufferParserBase<T> {
65 using BaseType = BufferParserBase<T>;
66 using RowType = io::RowType<T>;
67 using IndexSequence = typename RowType::IndexSequence;
68
69 using BaseType::BaseType;
70
71 void operator()(FieldBuffer buffer, const TypeBufferCategory& categories) {
72 if constexpr (!traits::kIsMappedToUserType<T>) {
73 InitRecordParser();
74 }
75
76 Integer field_count{0};
77 buffer.Read(field_count, BufferCategory::kPlainBuffer);
78
79 if (field_count != RowType::size) {
80 throw CompositeSizeMismatch(field_count, RowType::size,
81 compiler::GetTypeName<T>());
82 }
83
84 ReadTuple(buffer, categories, RowType::GetTuple(this->value),
85 IndexSequence{});
86 }
87
88 private:
89 static constexpr std::size_t int_size = sizeof(Integer);
90
91 template <typename U>
92 void ReadField(FieldBuffer& buffer, const TypeBufferCategory& categories,
93 U& val) const {
94 Integer field_type = 0;
95 buffer.Read(field_type, BufferCategory::kPlainBuffer);
96 auto elem_category = GetTypeBufferCategory(categories, field_type);
97 if (elem_category == BufferCategory::kNoParser) {
98 throw LogicError{"Buffer category for oid " + std::to_string(field_type) +
99 " is unknown"};
100 }
101 buffer.ReadRaw(val, categories, elem_category);
102 }
103 template <typename Tuple, std::size_t... Indexes>
104 void ReadTuple(FieldBuffer& buffer, const TypeBufferCategory& categories,
105 Tuple&& tuple, std::index_sequence<Indexes...>) const {
106 (ReadField(buffer, categories,
107 std::get<Indexes>(std::forward<Tuple>(tuple))),
108 ...);
109 }
110};
111
112template <typename T>
113struct CompositeBinaryFormatter : BufferFormatterBase<T> {
114 using BaseType = BufferFormatterBase<T>;
115 using PgMapping = CppToPg<T>;
116 using RowType = io::RowType<T>;
117 using IndexSequence = typename RowType::IndexSequence;
118 static constexpr std::size_t size = RowType::size;
119
120 using BaseType::BaseType;
121
122 template <typename Buffer>
123 void operator()(const UserTypes& types, Buffer& buffer) const {
124 const auto& type_desc =
125 types.GetCompositeDescription(PgMapping::GetOid(types));
126 if (type_desc.Size() != size) {
127 throw CompositeSizeMismatch{type_desc.Size(), size,
128 compiler::GetTypeName<T>()};
129 }
130 // Number of fields
131 io::WriteBuffer(types, buffer, static_cast<Integer>(size));
132 WriteTuple(types, type_desc, buffer, RowType::GetTuple(this->value),
133 IndexSequence{});
134 }
135
136 private:
137 template <typename Buffer, typename U>
138 void WriteField(const UserTypes& types,
139 const CompositeTypeDescription& type_desc, std::size_t index,
140 Buffer& buffer, const U& val) const {
141 auto field_type = CppToPg<U>::GetOid(types);
142 const auto& field_desc = type_desc[index];
143 if (field_type != field_desc.type) {
144 if (io::MappedToSameType(static_cast<PredefinedOids>(field_type),
145 static_cast<PredefinedOids>(
146 types.FindDomainBaseOid(field_desc.type)))) {
147 field_type = field_desc.type;
148 } else {
149 throw CompositeMemberTypeMismatch(
150 PgMapping::postgres_name.schema, PgMapping::postgres_name.name,
151 field_desc.name, field_desc.type, field_type);
152 }
153 }
154 io::WriteBuffer(types, buffer, static_cast<Integer>(field_type));
155 io::WriteRawBinary(types, buffer, val, field_type);
156 }
157 template <typename Buffer, typename Tuple, std::size_t... Indexes>
158 void WriteTuple(const UserTypes& types,
159 const CompositeTypeDescription& type_desc, Buffer& buffer,
160 Tuple&& tuple, std::index_sequence<Indexes...>) const {
161 (WriteField(types, type_desc, Indexes, buffer,
162 std::get<Indexes>(std::forward<Tuple>(tuple))),
163 ...);
164 }
165};
166
167} // namespace detail
168
169namespace traits {
170
171namespace detail {
172
173template <typename Tuple>
174struct AssertTupleHasParsers;
175
176template <typename... Members>
177struct AssertTupleHasParsers<std::tuple<Members...>> : std::true_type {
178 static_assert((HasParser<std::decay_t<Members>>::value && ...),
179 "No parser for member. Probably you forgot to include "
180 "file with parser or to define your own. Please see page "
181 "`uPg: Supported data types` for more information");
182};
183
184template <typename Tuple>
185struct AssertTupleHasFormatters;
186
187template <typename... Members>
188struct AssertTupleHasFormatters<std::tuple<Members...>> : std::true_type {
189 static_assert(
190 (HasFormatter<std::decay_t<Members>>::value && ...),
191 "No formatter for member. Probably you forgot to "
192 "include file with formatter or to define your own. Please see page "
193 "`uPg: Supported data types` for more information");
194};
195
196template <typename T>
197constexpr bool AssertHasCompositeParsers() {
198 static_assert(kIsRowType<T>);
199 return AssertTupleHasParsers<typename io::RowType<T>::TupleType>::value;
200}
201
202template <typename T>
203constexpr bool AssertHasCompositeFormatters() {
204 static_assert(kIsRowType<T>);
205 return AssertTupleHasFormatters<typename io::RowType<T>::TupleType>::value;
206}
207
208} // namespace detail
209
210template <typename T>
211struct Input<
213 static_assert(detail::AssertHasCompositeParsers<T>());
215};
216
217template <typename T>
220 static_assert(detail::AssertHasCompositeFormatters<T>());
222};
223
224template <typename T>
225struct ParserBufferCategory<io::detail::CompositeBinaryParser<T>>
227};
228
229} // namespace traits
230} // namespace storages::postgres::io
231
232USERVER_NAMESPACE_END