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