libs/http/include/boost/http/db/schema.hpp

100.0% Lines (38/38) 100.0% Functions (28/28) 100.0% Branches (1/1)
libs/http/include/boost/http/db/schema.hpp
Line Branch Hits Source Code
1 //
2 // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0.
5 // https://www.boost.org/LICENSE_1_0.txt
6 //
7
8 #ifndef BOOST_HTTP_DB_SCHEMA_HPP
9 #define BOOST_HTTP_DB_SCHEMA_HPP
10
11 #include <cstdint>
12 #include <tuple>
13 #include <type_traits>
14 #include <string_view>
15
16 namespace boost {
17 namespace http {
18 namespace db {
19
20 /** Bitwise flags describing column properties.
21
22 Accumulated on a @ref field_t descriptor via
23 builder-style member functions.
24 */
25 enum field_flags : unsigned
26 {
27 flag_none = 0,
28 flag_primary_key = 1 << 0,
29 flag_auto_increment = 1 << 1,
30 flag_not_null = 1 << 2,
31 flag_unique = 1 << 3,
32 flag_indexed = 1 << 4
33 };
34
35 /** Describe a single column mapped to a struct member.
36
37 The member pointer is stored as a data member so
38 that the natural syntax works without requiring
39 angle brackets. The entire schema description is
40 constexpr and available at compile time.
41
42 @par Example
43 @code
44 field("id", &user::id).primary_key().auto_increment()
45 field("email", &user::email).not_null().unique()
46 @endcode
47
48 @tparam T Member value type (e.g. `std::string`).
49 @tparam C Containing class type (e.g. `user`).
50
51 @see field, embed_t, has_one_t
52 */
53 template <typename T, typename C>
54 struct field_t
55 {
56 using value_type = T;
57 using class_type = C;
58
59 std::string_view name;
60 T C::* pointer;
61 unsigned flags = flag_none;
62
63 /// Mark this column as the primary key.
64 constexpr field_t& primary_key() { flags |= flag_primary_key; return *this; }
65
66 /// Mark this column as auto-incrementing.
67 constexpr field_t& auto_increment() { flags |= flag_auto_increment; return *this; }
68
69 /// Mark this column as NOT NULL.
70 constexpr field_t& not_null() { flags |= flag_not_null; return *this; }
71
72 /// Mark this column as UNIQUE.
73 constexpr field_t& unique() { flags |= flag_unique; return *this; }
74
75 /// Mark this column as indexed.
76 constexpr field_t& indexed() { flags |= flag_indexed; return *this; }
77
78 /// Return true if this column is a primary key.
79 7 constexpr bool is_primary_key() const { return flags & flag_primary_key; }
80
81 /// Return true if this column is auto-incrementing.
82 4 constexpr bool is_auto_increment() const { return flags & flag_auto_increment; }
83
84 /// Return true if this column is NOT NULL.
85 4 constexpr bool is_not_null() const { return flags & flag_not_null; }
86
87 /// Return true if this column is UNIQUE.
88 3 constexpr bool is_unique() const { return flags & flag_unique; }
89
90 /// Return true if this column is indexed.
91 2 constexpr bool is_indexed() const { return flags & flag_indexed; }
92
93 /// Return the member value from an object.
94 5 constexpr T const& get(C const& obj) const
95 {
96 5 return obj.*pointer;
97 }
98
99 /// Set the member value on an object.
100 1 constexpr void set(C& obj, T const& value) const
101 {
102 1 obj.*pointer = value;
103 1 }
104
105 /// Set the member value on an object by move.
106 2 constexpr void set(C& obj, T&& value) const
107 {
108 2 obj.*pointer = static_cast<T&&>(value);
109 2 }
110 };
111
112 /** Create a field descriptor for a member pointer.
113
114 Deduces `T` and `C` from the member pointer so
115 template arguments are never needed at the call site.
116
117 @par Example
118 @code
119 field("email", &user::email)
120 field("id", &user::id).primary_key().auto_increment()
121 @endcode
122
123 @param name Column name in the database table.
124 @param ptr Pointer to the mapped data member.
125
126 @return A @ref field_t descriptor for the member.
127
128 @see field_t
129 */
130 template <typename T, typename C>
131 constexpr auto field(std::string_view name, T C::* ptr)
132 -> field_t<T, C>
133 {
134 return { name, ptr };
135 }
136
137 /** Describe a nested struct flattened into the parent table.
138
139 The nested type must provide its own `tag_invoke`
140 overloads for @ref fields_t. A prefix is prepended
141 to each nested column name to avoid collisions.
142
143 @par Example
144 @code
145 // If user::addr is an address with field "street",
146 // the resulting column is "addr_street".
147 embed("addr_", &user::addr)
148 @endcode
149
150 @tparam T Nested struct type.
151 @tparam C Containing class type.
152
153 @see embed, field_t
154 */
155 template <typename T, typename C>
156 struct embed_t
157 {
158 using value_type = T;
159 using class_type = C;
160
161 std::string_view prefix;
162 T C::* pointer;
163
164 /// Return the nested struct from an object.
165 2 constexpr T const& get(C const& obj) const
166 {
167 2 return obj.*pointer;
168 }
169
170 /// Set the nested struct on an object.
171 1 constexpr void set(C& obj, T const& value) const
172 {
173 1 obj.*pointer = value;
174 1 }
175
176 /// Set the nested struct on an object by move.
177 1 constexpr void set(C& obj, T&& value) const
178 {
179 1 obj.*pointer = static_cast<T&&>(value);
180 1 }
181 };
182
183 /** Create an embedded field descriptor for a nested struct.
184
185 @param prefix String prepended to nested column names.
186 @param ptr Pointer to the nested data member.
187
188 @return An @ref embed_t descriptor.
189
190 @see embed_t
191 */
192 template <typename T, typename C>
193 constexpr auto embed(std::string_view prefix, T C::* ptr)
194 -> embed_t<T, C>
195 {
196 return { prefix, ptr };
197 }
198
199 /** Describe a one-to-one relationship to another table.
200
201 The referenced type must provide its own
202 `tag_invoke` overloads for @ref table_name_t
203 and @ref fields_t.
204
205 @par Example
206 @code
207 has_one("address_id", &user::addr)
208 @endcode
209
210 @tparam T Referenced struct type.
211 @tparam C Containing class type.
212
213 @see has_one, has_many_t
214 */
215 template <typename T, typename C>
216 struct has_one_t
217 {
218 using value_type = T;
219 using class_type = C;
220
221 std::string_view foreign_key;
222 T C::* pointer;
223
224 /// Return the related object from the parent.
225 1 constexpr T const& get(C const& obj) const
226 {
227 1 return obj.*pointer;
228 }
229
230 /// Set the related object on the parent.
231 1 constexpr void set(C& obj, T const& value) const
232 {
233 1 obj.*pointer = value;
234 1 }
235
236 /// Set the related object on the parent by move.
237 1 constexpr void set(C& obj, T&& value) const
238 {
239 1 obj.*pointer = static_cast<T&&>(value);
240 1 }
241 };
242
243 /** Create a one-to-one relationship descriptor.
244
245 @param foreign_key Column name of the foreign key.
246 @param ptr Pointer to the related data member.
247
248 @return A @ref has_one_t descriptor.
249
250 @see has_one_t
251 */
252 template <typename T, typename C>
253 constexpr auto has_one(std::string_view foreign_key, T C::* ptr)
254 -> has_one_t<T, C>
255 {
256 return { foreign_key, ptr };
257 }
258
259 /** Describe a one-to-many relationship.
260
261 The child type has a member acting as the foreign
262 key pointing back to the parent's primary key.
263 The child's foreign key member pointer is stored
264 so the library can build the appropriate JOIN or
265 subquery.
266
267 @par Example
268 @code
269 has_many(&user::posts, &post::user_id)
270 @endcode
271
272 @tparam Collection Container type in the parent
273 (e.g. `std::vector< post >`).
274 @tparam Parent Parent class type.
275 @tparam FK Foreign key value type in the child.
276 @tparam Child Child class type.
277
278 @see has_many, has_one_t
279 */
280 template <typename Collection, typename Parent, typename FK, typename Child>
281 struct has_many_t
282 {
283 using collection_type = Collection;
284 using parent_type = Parent;
285 using child_type = Child;
286 using fk_value_type = FK;
287
288 Collection Parent::* pointer;
289 FK Child::* foreign_key;
290 };
291
292 /** Create a one-to-many relationship descriptor.
293
294 @param ptr Pointer to the collection member in the parent.
295 @param fk Pointer to the foreign key member in the child.
296
297 @return A @ref has_many_t descriptor.
298
299 @see has_many_t
300 */
301 template <
302 typename Collection, typename Parent,
303 typename FK, typename Child>
304 constexpr auto has_many(
305 Collection Parent::* ptr,
306 FK Child::* fk)
307 -> has_many_t<Collection, Parent, FK, Child>
308 {
309 return { ptr, fk };
310 }
311
312 /** Tag type for retrieving the table name of a mapped type.
313
314 Customize via `tag_invoke`:
315
316 @par Example
317 @code
318 constexpr auto tag_invoke(
319 db::table_name_t, user const&)
320 {
321 return "users";
322 }
323 @endcode
324
325 @see fields_t, HasMapping
326 */
327 struct table_name_t
328 {
329 template <typename T>
330 3 constexpr auto operator()(T const& v) const
331 {
332 3 return tag_invoke(*this, v);
333 }
334 };
335
336 /** Tag type for retrieving the field descriptors of a mapped type.
337
338 Customize via `tag_invoke`:
339
340 @par Example
341 @code
342 constexpr auto tag_invoke(
343 db::fields_t, user const&)
344 {
345 return std::tuple(
346 db::field("id", &user::id)
347 .primary_key().auto_increment(),
348 db::field("email", &user::email)
349 .not_null().unique(),
350 db::field("name", &user::name));
351 }
352 @endcode
353
354 @see table_name_t, HasMapping
355 */
356 struct fields_t
357 {
358 template <typename T>
359 constexpr auto operator()(T const& v) const
360 {
361 return tag_invoke(*this, v);
362 }
363 };
364
365 /// Customization point object for @ref table_name_t.
366 inline constexpr table_name_t table_name{};
367
368 /// Customization point object for @ref fields_t.
369 inline constexpr fields_t fields{};
370
371 /** Concept for types with a complete schema mapping.
372
373 A conforming type must provide `tag_invoke`
374 overloads for both @ref table_name_t and
375 @ref fields_t.
376
377 @par Syntactic Requirements
378 @li `tag_invoke( table_name, v )` is convertible
379 to `std::string_view`.
380 @li `tag_invoke( fields, v )` is a valid expression.
381
382 @tparam T The type to check for a schema mapping.
383
384 @see table_name_t, fields_t
385 */
386 template <typename T>
387 concept HasMapping = requires(T const& v)
388 {
389 { tag_invoke(table_name, v) } -> std::convertible_to<std::string_view>;
390 { tag_invoke(fields, v) };
391 };
392
393 namespace detail {
394
395 template <typename Tuple, typename F, std::size_t... Is>
396 3 constexpr void for_each_impl(
397 Tuple const& t, F&& f, std::index_sequence<Is...>)
398 {
399 3 (f(std::get<Is>(t)), ...);
400 3 }
401
402 } // namespace detail
403
404 /** Invoke a callable for each field in a mapped type.
405
406 @param f Callable invoked as `f( field_descriptor )`
407 for every field in `T`'s mapping.
408
409 @tparam T A type satisfying @ref HasMapping.
410
411 @see field_count
412 */
413 template <HasMapping T, typename F>
414 3 constexpr void for_each_field(F&& f)
415 {
416 3 constexpr auto fs = tag_invoke(fields, T{});
417
1/1
✓ Branch 1 taken 3 times.
3 detail::for_each_impl(
418 fs,
419 static_cast<F&&>(f),
420 std::make_index_sequence<
421 std::tuple_size_v<decltype(fs)>>{});
422 3 }
423
424 /** Return the number of fields in a mapped type.
425
426 @tparam T A type satisfying @ref HasMapping.
427
428 @return The compile-time field count.
429
430 @see for_each_field
431 */
432 template <HasMapping T>
433 constexpr std::size_t field_count()
434 {
435 constexpr auto fs = tag_invoke(fields, T{});
436 return std::tuple_size_v<decltype(fs)>;
437 }
438
439 } // namespace db
440 } // namespace http
441 } // namespace boost
442
443 #endif
444