userver: userver/storages/postgres/io/composite_types.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
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 <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