userver: userver/storages/postgres/io/bitstring.hpp Source File
Loading...
Searching...
No Matches
bitstring.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/io/bitstring.hpp
4/// @brief storages::postgres::BitString I/O support
5/// @ingroup userver_postgres_parse_and_format
6
7#include <bitset>
8#include <vector>
9
10#include <userver/storages/postgres/exceptions.hpp>
11#include <userver/storages/postgres/io/buffer_io.hpp>
12#include <userver/storages/postgres/io/buffer_io_base.hpp>
13#include <userver/storages/postgres/io/type_mapping.hpp>
14
15#include <userver/utils/flags.hpp>
16
17USERVER_NAMESPACE_BEGIN
18
19namespace storages::postgres {
20
21namespace io::traits {
22
23template <typename BitContainer>
24struct IsBitStringCompatible : std::is_integral<BitContainer> {};
25
26template <typename Enum>
27struct IsBitStringCompatible<USERVER_NAMESPACE::utils::Flags<Enum>> : std::true_type {};
28
29template <std::size_t N>
30struct IsBitStringCompatible<std::bitset<N>> : std::true_type {};
31
32template <std::size_t N>
33struct IsBitStringCompatible<std::array<bool, N>> : std::true_type {};
34
35template <typename T>
36inline constexpr bool kIsBitStringCompatible = IsBitStringCompatible<T>::value;
37
38template <typename BitContainer, typename Enable = void>
39struct BitContainerTraits;
40
41template <typename BitContainer>
43 static bool TestBit(const BitContainer& bits, std::uint8_t i) { return bits & (1ull << i); }
44 static void SetBit(BitContainer& bits, std::uint8_t i) { bits |= (1ull << i); }
45 static constexpr std::size_t BitCount() noexcept { return sizeof(BitContainer) * 8; }
46 static void Reset(BitContainer& bits) noexcept { bits = 0; }
47};
48
49template <std::size_t N>
50struct BitContainerTraits<std::array<bool, N>> {
51 static_assert(N > 0, "Length for bit container must be at least 1");
52 using BitContainer = std::array<bool, N>;
53 static bool TestBit(const BitContainer& bits, std::uint8_t i) { return bits[i]; }
54 static void SetBit(BitContainer& bits, std::uint8_t i) { bits[i] = true; }
55 static constexpr std::size_t BitCount() noexcept { return N; }
56 static void Reset(BitContainer& bits) noexcept { bits.fill(false); }
57};
58
59template <std::size_t N>
61 static_assert(N > 0, "Length for bit container must be at least 1");
62 using BitContainer = std::bitset<N>;
63 static bool TestBit(const BitContainer& bits, std::uint8_t i) { return bits.test(i); }
64 static void SetBit(BitContainer& bits, std::uint8_t i) { bits.set(i); }
65 static constexpr std::size_t BitCount() noexcept { return N; }
66 static void Reset(BitContainer& bits) noexcept { bits.reset(); }
67};
68
69} // namespace io::traits
70
71enum class BitStringType { kBit, kBitVarying };
72
73namespace detail {
74
75enum class BitContainerInterface { kCommon, kFlags };
76
77template <typename BitContainerRef, BitContainerInterface, BitStringType>
78struct BitStringRefWrapper {
79 static_assert(std::is_reference<BitContainerRef>::value, "The container must be passed by reference");
80
81 using BitContainer = std::decay_t<BitContainerRef>;
82 static_assert(
83 io::traits::kIsBitStringCompatible<BitContainer>,
84 "This C++ type cannot be used with PostgreSQL 'bit' and 'bit "
85 "varying' data type"
86 );
87
88 BitContainerRef bits;
89};
90
91} // namespace detail
92
93template <typename BitContainer, BitStringType>
95 static_assert(!std::is_reference<BitContainer>::value, "The container must not be passed by reference");
96
97 static_assert(
98 io::traits::kIsBitStringCompatible<BitContainer>,
99 "This C++ type cannot be used with PostgreSQL 'bit' and 'bit "
100 "varying' data type"
101 );
102
103 BitContainer bits{};
104};
105
106template <BitStringType kBitStringType, typename BitContainer>
107constexpr detail::BitStringRefWrapper<const BitContainer&, detail::BitContainerInterface::kCommon, kBitStringType>
108BitString(const BitContainer& bits) {
109 return {bits};
110}
111
112template <BitStringType kBitStringType, typename BitContainer>
113constexpr detail::BitStringRefWrapper<BitContainer&, detail::BitContainerInterface::kCommon, kBitStringType> BitString(
114 BitContainer& bits
115) {
116 return {bits};
117}
118
119template <BitStringType kBitStringType, typename Enum>
120constexpr detail::BitStringRefWrapper<
121 const USERVER_NAMESPACE::utils::Flags<Enum>&,
122 detail::BitContainerInterface::kFlags,
123 kBitStringType>
124BitString(const USERVER_NAMESPACE::utils::Flags<Enum>& bits) {
125 return {bits};
126}
127
128template <BitStringType kBitStringType, typename Enum>
129constexpr detail::
130 BitStringRefWrapper<USERVER_NAMESPACE::utils::Flags<Enum>&, detail::BitContainerInterface::kFlags, kBitStringType>
131 BitString(USERVER_NAMESPACE::utils::Flags<Enum>& bits) {
132 return {bits};
133}
134
135template <typename BitContainer>
136constexpr auto Varbit(BitContainer&& bits) {
137 return BitString<BitStringType::kBitVarying>(std::forward<BitContainer>(bits));
138}
139
140template <typename BitContainer>
141constexpr auto Bit(BitContainer&& bits) {
142 return BitString<BitStringType::kBit>(std::forward<BitContainer>(bits));
143}
144
145namespace io {
146
147template <typename BitContainerRef, BitStringType kBitStringType>
148struct BufferParser<
149 postgres::detail::
150 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kCommon, kBitStringType>>
151 : detail::BufferParserBase<postgres::detail::BitStringRefWrapper<
152 BitContainerRef,
153 postgres::detail::BitContainerInterface::kCommon,
154 kBitStringType>&&> {
155 using BitContainer = std::decay_t<BitContainerRef>;
156 using BaseType = detail::BufferParserBase<
157 postgres::detail::
158 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kCommon, kBitStringType>&&>;
159 using BaseType::BaseType;
160
161 void operator()(FieldBuffer buffer) {
162 Integer bit_count{0};
163 buffer.Read(bit_count, BufferCategory::kPlainBuffer);
164 if (static_cast<std::size_t>((bit_count + 7) / 8) > buffer.length) throw InvalidBitStringRepresentation{};
165
166 auto& bits = this->value.bits;
167 if (const Integer target_bit_count = io::traits::BitContainerTraits<BitContainer>::BitCount();
168 target_bit_count < bit_count) {
169 throw BitStringOverflow(bit_count, target_bit_count);
170 }
171
172 // buffer contains a zero-padded bitstring, most significant bit first
173 io::traits::BitContainerTraits<BitContainer>::Reset(bits);
174 for (Integer i = 0; i < bit_count; ++i) {
175 const auto* byte_cptr = buffer.buffer + (i / 8);
176 if ((*byte_cptr) & (0x80 >> (i % 8))) {
177 io::traits::BitContainerTraits<BitContainer>::SetBit(bits, bit_count - i - 1);
178 }
179 }
180 }
181};
182
183template <typename BitContainerRef, BitStringType kBitStringType>
184struct BufferParser<
185 postgres::detail::
186 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kFlags, kBitStringType>>
187 : detail::BufferParserBase<
188 postgres::detail::
189 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kFlags, kBitStringType>&&> {
190 using BitContainer = std::decay_t<BitContainerRef>;
191 using BaseType = detail::BufferParserBase<
192 postgres::detail::
193 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kFlags, kBitStringType>&&>;
194 using BaseType::BaseType;
195
196 void operator()(FieldBuffer buffer) {
197 typename BitContainer::ValueType bits{0};
198 ReadBuffer(buffer, BitString<kBitStringType>(bits));
199 this->value.bits.SetValue(bits);
200 }
201};
202
203template <typename BitContainer, BitStringType kBitStringType>
204struct BufferParser<postgres::BitStringWrapper<BitContainer, kBitStringType>>
205 : detail::BufferParserBase<postgres::BitStringWrapper<BitContainer, kBitStringType>> {
206 using BaseType = detail::BufferParserBase<postgres::BitStringWrapper<BitContainer, kBitStringType>>;
207 using BaseType::BaseType;
208
209 void operator()(const FieldBuffer& buffer) { ReadBuffer(buffer, BitString<kBitStringType>(this->value.bits)); }
210};
211
212template <std::size_t N>
213struct BufferParser<std::bitset<N>> : detail::BufferParserBase<std::bitset<N>> {
214 using BaseType = detail::BufferParserBase<std::bitset<N>>;
215 using BaseType::BaseType;
216
217 void operator()(const FieldBuffer& buffer) { ReadBuffer(buffer, Varbit(this->value)); }
218};
219
220template <typename BitContainerRef, BitStringType kBitStringType>
221struct BufferFormatter<
222 postgres::detail::
223 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kCommon, kBitStringType>>
224 : detail::BufferFormatterBase<
225 postgres::detail::
226 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kCommon, kBitStringType>> {
227 using BitContainer = std::decay_t<BitContainerRef>;
228 using BaseType = detail::BufferFormatterBase<
229 postgres::detail::
230 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kCommon, kBitStringType>>;
231 using BaseType::BaseType;
232
233 template <typename Buffer>
234 void operator()(const UserTypes& types, Buffer& buffer) const {
235 // convert bitcontainer to bytes and write into buffer,
236 // from most to least significant
237 const auto& bits = this->value.bits;
238 constexpr auto bit_count = io::traits::BitContainerTraits<BitContainer>::BitCount();
239
240 std::array<std::uint8_t, (bit_count + 7) / 8> data{};
241 for (std::size_t i = 0; i < bit_count; ++i) {
242 data[i / 8] |=
243 static_cast<std::uint8_t>(io::traits::BitContainerTraits<BitContainer>::TestBit(bits, bit_count - i - 1)
244 )
245 << (7 - i % 8);
246 }
247
248 buffer.reserve(buffer.size() + sizeof(Integer) + data.size());
249 WriteBuffer(types, buffer, static_cast<Integer>(bit_count));
250 buffer.insert(buffer.end(), data.begin(), data.end());
251 }
252};
253
254template <typename BitContainerRef, BitStringType kBitStringType>
255struct BufferFormatter<
256 postgres::detail::
257 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kFlags, kBitStringType>>
258 : detail::BufferFormatterBase<
259 postgres::detail::
260 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kFlags, kBitStringType>> {
261 using BitContainer = std::decay_t<BitContainerRef>;
262 using BaseType = detail::BufferFormatterBase<
263 postgres::detail::
264 BitStringRefWrapper<BitContainerRef, postgres::detail::BitContainerInterface::kFlags, kBitStringType>>;
265 using BaseType::BaseType;
266
267 template <typename Buffer>
268 void operator()(const UserTypes& types, Buffer& buffer) const {
269 WriteBuffer(types, buffer, BitString<kBitStringType>(this->value.bits.GetValue()));
270 }
271};
272
273template <typename BitContainer, BitStringType kBitStringType>
274struct BufferFormatter<postgres::BitStringWrapper<BitContainer, kBitStringType>>
275 : detail::BufferFormatterBase<postgres::BitStringWrapper<BitContainer, kBitStringType>> {
276 using BaseType = detail::BufferFormatterBase<postgres::BitStringWrapper<BitContainer, kBitStringType>>;
277 using BaseType::BaseType;
278
279 template <typename Buffer>
280 void operator()(const UserTypes& types, Buffer& buffer) const {
281 WriteBuffer(types, buffer, BitString<kBitStringType>(this->value.bits));
282 }
283};
284
285// std::bitset is saved as bit varying on default
286
287template <std::size_t N>
288struct BufferFormatter<std::bitset<N>> : detail::BufferFormatterBase<std::bitset<N>> {
289 using BitContainer = std::bitset<N>;
290 using BaseType = detail::BufferFormatterBase<std::bitset<N>>;
291 using BaseType::BaseType;
292
293 template <typename Buffer>
294 void operator()(const UserTypes& types, Buffer& buffer) const {
295 WriteBuffer(types, buffer, Varbit(this->value));
296 }
297};
298
299template <typename BitContainer, postgres::detail::BitContainerInterface kContainerInterface>
300struct CppToSystemPg<
301 postgres::detail::BitStringRefWrapper<BitContainer, kContainerInterface, postgres::BitStringType::kBitVarying>>
302 : PredefinedOid<PredefinedOids::kVarbit> {};
303template <typename BitContainer>
304struct CppToSystemPg<postgres::BitStringWrapper<BitContainer, postgres::BitStringType::kBitVarying>>
305 : PredefinedOid<PredefinedOids::kVarbit> {};
306
307template <typename BitContainer, postgres::detail::BitContainerInterface kContainerInterface>
308struct CppToSystemPg<
309 postgres::detail::BitStringRefWrapper<BitContainer, kContainerInterface, postgres::BitStringType::kBit>>
310 : PredefinedOid<PredefinedOids::kBit> {};
311template <typename BitContainer>
312struct CppToSystemPg<postgres::BitStringWrapper<BitContainer, postgres::BitStringType::kBit>>
313 : PredefinedOid<PredefinedOids::kBit> {};
314
315template <std::size_t N>
316struct CppToSystemPg<std::bitset<N>> : PredefinedOid<PredefinedOids::kVarbit> {};
317
318} // namespace io
319} // namespace storages::postgres
320
321USERVER_NAMESPACE_END