Line data 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 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
|