Lightweight 0.20260625.0
Loading...
Searching...
No Matches
Record.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../DataBinder/SqlGuid.hpp"
6#include "../Utils.hpp"
7#include "BelongsTo.hpp"
8#include "Field.hpp"
9
10#include <reflection-cpp/reflection.hpp>
11
12#include <concepts>
13#include <limits>
14#include <optional>
15#include <string_view>
16#include <tuple>
17
18namespace Lightweight
19{
20
21/// @brief Represents a sequence of indexes that can be used alongside Query() to retrieve only part of the record.
22///
23/// @ingroup DataMapper
24template <size_t... Ints>
25using SqlElements = std::integer_sequence<size_t, Ints...>;
26
27namespace detail
28{
29 // Helper trait to detect specializations of SqlElements
30 template <typename T>
31 struct IsSqlElements: std::false_type
32 {
33 };
34
35 template <size_t... Ints>
36 struct IsSqlElements<SqlElements<Ints...>>: std::true_type
37 {
38 };
39} // namespace detail
40
41// @brief Helper concept to check if a type is not a specialization of SqlElements
42template <typename T>
43concept NotSqlElements = !detail::IsSqlElements<T>::value;
44
45/// @brief Represents a record type that can be used with the DataMapper.
46///
47/// The record type must be an aggregate type.
48///
49/// @see DataMapper, Field, BelongsTo, HasMany, HasManyThrough, HasOneThrough
50/// @ingroup DataMapper
51template <typename Record>
52concept DataMapperRecord = std::is_aggregate_v<Record> && NotSqlElements<Record>;
53
54template <typename... Records>
55concept DataMapperRecords = (DataMapperRecord<Records> && ...);
56
57namespace detail
58{
59
60 template <std::size_t I, typename Record>
61 constexpr std::optional<size_t> FindPrimaryKeyIndex()
62 {
63 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
64 if constexpr (I < RecordMemberCount<Record>)
65 {
66 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
67 return { I };
68 else
69 return FindPrimaryKeyIndex<I + 1, Record>();
70 }
71 return std::nullopt;
72 }
73
74} // namespace detail
75
76/// Declare RecordPrimaryKeyIndex<Record> to retrieve the primary key index of the given record.
77template <typename Record>
78constexpr size_t RecordPrimaryKeyIndex =
79 detail::FindPrimaryKeyIndex<0, Record>().value_or((std::numeric_limits<size_t>::max)());
80
81/// Retrieves a reference to the given record's primary key.
82template <typename Record>
83decltype(auto) RecordPrimaryKeyOf(Record&& record)
84{
85 // static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
86 // static_assert(RecordPrimaryKeyIndex<Record> != static_cast<size_t>(-1), "Record must have a primary key");
87 return GetRecordMemberAt<RecordPrimaryKeyIndex<std::remove_cvref_t<Record>>>(std::forward<Record>(record));
88}
89
90namespace details
91{
92
93 template <typename Record>
94 struct RecordPrimaryKeyTypeHelper
95 {
96 using type = std::monostate;
97 };
98
99 template <typename Record>
100 requires(RecordPrimaryKeyIndex<Record> < RecordMemberCount<Record>)
101 struct RecordPrimaryKeyTypeHelper<Record>
102 {
103 using type = RecordMemberTypeOf<RecordPrimaryKeyIndex<Record>, Record>::ValueType;
104 };
105
106} // namespace details
107
108/// Reflects the primary key type of the given record.
109template <typename Record>
110using RecordPrimaryKeyType = details::RecordPrimaryKeyTypeHelper<Record>::type;
111
112/// @brief Selector value meaning "resolve the relationship automatically; the match must be unique".
113///
114/// This is the default for every relationship selector. Pass a `SqlRealName` instead to name the
115/// foreign key column explicitly, which is required when a record holds more than one foreign key
116/// into the same table.
117///
118/// @ingroup DataMapper
119inline constexpr std::nullopt_t AutoDetectRelation = std::nullopt;
120
121/// @brief Constrains what may be used to single out one of several foreign keys into the same table.
122///
123/// Two forms are accepted:
124/// - `std::nullopt` (`AutoDetectRelation`) - resolve automatically; ambiguity is a compile error.
125/// - a `SqlRealName` (or anything convertible to `std::string_view`) - the SQL column name of the
126/// foreign key to use.
127///
128/// A relationship selector must stay inert at *declaration* time: the two records of a relationship
129/// reference each other, so neither is complete where the other is declared. That rules out
130/// pointer-to-member selectors and is why the foreign key is named by its column.
131///
132/// @ingroup DataMapper
133template <auto Selector>
135 std::same_as<std::remove_cvref_t<decltype(Selector)>, std::nullopt_t> || requires { std::string_view { Selector }; };
136
137/// @brief Marks the join record of a relationship that is reached *through* an intermediate table.
138///
139/// Writing the join record bare - `HasManyThrough<Person, Friendship>` - leaves the reader to
140/// remember which of the two record types is the join table. Wrapping it says so at the call site:
141///
142/// @code
143/// struct Person
144/// {
145/// Field<int, PrimaryKey::AutoAssign> id;
146/// HasManyThrough<Person, Through<Friendship>> friends;
147/// };
148/// @endcode
149///
150/// The bare spelling still compiles, but is deprecated and will be removed in a future release.
151///
152/// @tparam JoinRecordT The join record type, holding the foreign keys of the relationship.
153///
154/// @see HasManyThrough, HasOneThrough
155/// @ingroup DataMapper
156template <typename JoinRecordT>
158{
159 /// The join record type this marker wraps.
160 using RecordType = JoinRecordT;
161};
162
163namespace detail
164{
165
166 template <typename T>
167 struct IsThroughType: std::false_type
168 {
169 };
170
171 template <typename JoinRecordT>
172 struct IsThroughType<Through<JoinRecordT>>: std::true_type
173 {
174 };
175
176} // namespace detail
177
178/// Tests whether @p T is a Through marker.
179/// @ingroup DataMapper
180template <typename T>
181constexpr bool IsThrough = detail::IsThroughType<std::remove_cvref_t<T>>::value;
182
183namespace detail
184{
185
186 /// @brief Resolves the bare (unwrapped) spelling of a join record, which is deprecated.
187 ///
188 /// The deprecation sits on the @ref DeprecatedSpelling member rather than on the class itself,
189 /// so that it is diagnosed when this template is *instantiated* with a bare join record. Marking
190 /// the class deprecated instead makes GCC diagnose the mention of the (non-dependent) template
191 /// name below - once per translation unit, for every user, including those who already wrap the
192 /// join record in Through<>.
193 template <typename JoinRecordT>
194 struct BareThroughRecord
195 {
196 using type = JoinRecordT;
197
198 /// Named from a dependent context below purely to raise the deprecation warning.
199 [[deprecated("Naming the join record directly is deprecated, wrap it as Through<T>, "
200 "e.g. HasManyThrough<Person, Through<Friendship>>.")]]
201 static constexpr bool DeprecatedSpelling = true;
202 };
203
204 /// Maps a join record specification onto the join record itself.
205 template <typename ThroughSpec>
206 struct ThroughRecordOfHelper
207 {
208 static_assert(BareThroughRecord<ThroughSpec>::DeprecatedSpelling);
209 using type = typename BareThroughRecord<ThroughSpec>::type;
210 };
211
212 template <typename JoinRecordT>
213 struct ThroughRecordOfHelper<Through<JoinRecordT>>
214 {
215 static_assert(!IsThrough<JoinRecordT>,
216 "Through<Through<T>> is not a valid join record specification, write Through<T>.");
217 using type = JoinRecordT;
218 };
219
220} // namespace detail
221
222/// @brief Resolves the join record of a through-relationship from its template argument.
223///
224/// Accepts both the `Through<T>` marker and the deprecated bare `T` spelling, yielding `T` either way.
225///
226/// @tparam ThroughSpec Either `Through<T>` or, deprecated, `T`.
227///
228/// @see Through
229/// @ingroup DataMapper
230template <typename ThroughSpec>
231using ThroughRecordOf = typename detail::ThroughRecordOfHelper<ThroughSpec>::type;
232
233namespace detail
234{
235
236 /// @brief Tests whether member @p I of @p Record is singled out by @p Selector.
237 ///
238 /// @tparam Selector The relationship selector, see the RelationSelector concept.
239 /// @tparam I Member index within @p Record.
240 /// @tparam Record The record holding the foreign key.
241 /// @return `true` when the selector accepts the member.
242 template <auto Selector, size_t I, typename Record>
243 constexpr bool RelationSelectorMatches()
244 {
245 if constexpr (std::same_as<std::remove_cvref_t<decltype(Selector)>, std::nullopt_t>)
246 return true;
247 else
248 return Lightweight::FieldNameAt<I, Record> == std::string_view { Selector };
249 }
250
251 /// @brief Outcome of scanning a child record for the `BelongsTo` members pointing back to an owner record.
252 struct InverseBelongsToLookup
253 {
254 /// Member index of the first matching `BelongsTo`, or `RecordMemberCount` if there is none.
255 size_t index {};
256
257 /// Number of matching `BelongsTo` members found.
258 size_t count {};
259
260 /// Number of `BelongsTo` members referencing the owner record, before the selector was applied.
261 size_t candidates {};
262 };
263
264 /// @brief Scans @p ChildRecord for the `BelongsTo` members whose referenced record is @p OwnerRecord.
265 ///
266 /// @tparam OwnerRecord The record on the "one" side of a one-to-many relationship.
267 /// @tparam ChildRecord The record on the "many" side, holding the foreign key.
268 /// @tparam Selector Singles out one of several foreign keys, see the RelationSelector concept.
269 /// @return The index of the first match, how many matches exist, and how many candidates the
270 /// selector had to choose from.
271 template <typename OwnerRecord, typename ChildRecord, auto Selector = AutoDetectRelation>
272 constexpr InverseBelongsToLookup FindInverseBelongsTo()
273 {
274 return FoldRecordMembers<ChildRecord>(
275 InverseBelongsToLookup { .index = RecordMemberCount<ChildRecord>, .count = 0, .candidates = 0 },
276 []<size_t I, typename MemberType>(InverseBelongsToLookup const accum) constexpr -> InverseBelongsToLookup {
277 // The two conditions must nest: `MemberType::ReferencedRecord` does not exist on plain
278 // fields, and `&&` inside a single `if constexpr` would still instantiate it.
279 if constexpr (IsBelongsTo<MemberType>)
280 {
281 if constexpr (std::same_as<typename MemberType::ReferencedRecord, OwnerRecord>)
282 {
283 if constexpr (RelationSelectorMatches<Selector, I, ChildRecord>())
284 return { .index = accum.count == 0 ? I : accum.index,
285 .count = accum.count + 1,
286 .candidates = accum.candidates + 1 };
287 else
288 return { .index = accum.index, .count = accum.count, .candidates = accum.candidates + 1 };
289 }
290 else
291 return accum;
292 }
293 else
294 return accum;
295 });
296 }
297
298 /// @brief Resolves - and validates - the inverse `BelongsTo` member of a relationship.
299 ///
300 /// Instantiating this template fails to compile unless @p ChildRecord declares exactly one
301 /// `BelongsTo` member that references @p OwnerRecord and is accepted by @p Selector. The compiler's
302 /// instantiation backtrace names the record types involved.
303 ///
304 /// @tparam OwnerRecord The record being referenced (the "one" side of the relationship).
305 /// @tparam ChildRecord The record holding the foreign key (the "many" side).
306 /// @tparam Selector Singles out one of several foreign keys, see the RelationSelector concept.
307 template <typename OwnerRecord, typename ChildRecord, auto Selector = AutoDetectRelation>
309 struct InverseBelongsToResolver
310 {
311 /// The raw lookup result for @p OwnerRecord within @p ChildRecord.
312 static constexpr InverseBelongsToLookup Lookup = FindInverseBelongsTo<OwnerRecord, ChildRecord, Selector>();
313
314 static_assert(Lookup.candidates != 0,
315 "This relationship requires the referencing record to declare a BelongsTo member pointing at "
316 "the referenced record's primary key. No such member was found. "
317 "See the instantiation backtrace below for the two record types involved.");
318
319 static_assert(Lookup.candidates == 0 || Lookup.count != 0,
320 "No BelongsTo member matches the foreign key column named by this relationship. The referencing "
321 "record does declare a BelongsTo pointing at the referenced record, but none of them uses that "
322 "column name - check the SqlRealName spelling on both sides. "
323 "See the instantiation backtrace below for the two record types involved.");
324
325 static_assert(Lookup.count <= 1,
326 "This relationship is ambiguous: the referencing record declares more than one BelongsTo member "
327 "pointing at the referenced record's primary key. Name the foreign key column to disambiguate, "
328 "e.g. HasMany<Child, SqlRealName { \"owner_id\" }>. "
329 "See the instantiation backtrace below for the two record types involved.");
330
331 /// Member index of the inverse `BelongsTo` inside @p ChildRecord.
332 /// Clamped to 0 on failure so that the static_asserts above are the only diagnostics emitted.
333 static constexpr size_t Index = Lookup.count == 1 ? Lookup.index : 0;
334 };
335
336} // namespace detail
337
338/// @brief Member index, within @p ChildRecord, of the `BelongsTo` member that points back to @p OwnerRecord.
339///
340/// This is how `HasMany`, `HasManyThrough` and `HasOneThrough` locate their foreign key column: by matching
341/// the relationship *type*, never by member position. Using it is a hard compile-time error when
342/// @p ChildRecord declares no such `BelongsTo`, or when it declares more than one and @p Selector does not
343/// single out exactly one of them.
344///
345/// @tparam OwnerRecord The record being referenced (the "one" side of the relationship).
346/// @tparam ChildRecord The record holding the foreign key (the "many" side).
347/// @tparam Selector Singles out one of several foreign keys, see the RelationSelector concept.
348///
349/// @ingroup DataMapper
350template <typename OwnerRecord, typename ChildRecord, auto Selector = AutoDetectRelation>
351constexpr size_t InverseBelongsToIndexOf = detail::InverseBelongsToResolver<OwnerRecord, ChildRecord, Selector>::Index;
352
353/// @brief SQL column name of the foreign key that links @p ChildRecord back to @p OwnerRecord.
354///
355/// @tparam OwnerRecord The record being referenced (the "one" side of the relationship).
356/// @tparam ChildRecord The record holding the foreign key (the "many" side).
357/// @tparam Selector Singles out one of several foreign keys, see the RelationSelector concept.
358///
359/// @ingroup DataMapper
360template <typename OwnerRecord, typename ChildRecord, auto Selector = AutoDetectRelation>
361constexpr std::string_view InverseBelongsToFieldNameOf =
362 FieldNameAt<InverseBelongsToIndexOf<OwnerRecord, ChildRecord, Selector>, ChildRecord>;
363
364/// @brief Maps the fields of the given record to the target that supports the operator[].
365template <typename Record, typename TargetMappable>
366void MapFromRecordFields(Record&& record, TargetMappable& target)
367{
368 EnumerateRecordMembers(std::forward<Record>(record), [&]<std::size_t I>(auto const& field) {
369 using MemberType = RecordMemberTypeOf<I, Record>;
370 static_assert(IsField<MemberType>, "Record member must be a Field<> type");
371 static_assert(std::is_assignable_v<decltype(target[I]), decltype(field.Value())>,
372 "Target must support operator[] with the field type");
373 target[I] = field.Value();
374 });
375}
376
377/// Requires that T satisfies to be a field with storage.
378///
379/// @ingroup DataMapper
380template <typename T>
381concept FieldWithStorage = requires(T const& field, T& mutableField) {
382 // clang-format off
383 { field.Value() } -> std::convertible_to<typename T::ValueType const&>;
384 { mutableField.MutableValue() } -> std::convertible_to<typename T::ValueType&>;
385 { field.IsModified() } -> std::convertible_to<bool>;
386 { mutableField.SetModified(bool {}) } -> std::convertible_to<void>;
387 // clang-format on
388};
389
390/// @brief Requires that T maps onto a column of its record's table.
391///
392/// This is satisfied by fields with storage (Field, BelongsTo) as well as by plain record members
393/// that are directly bindable as an output column (a record may be a plain struct of bindable
394/// members). Relation members (HasMany, HasManyThrough, HasOneThrough, ...) have no column of their
395/// own and therefore must be skipped by every column-enumerating code path, such as the projection
396/// of a SELECT statement.
397///
398/// @ingroup DataMapper
399template <typename T>
400concept RecordColumnMember = FieldWithStorage<T> || SqlOutputColumnBinder<T>;
401
402/// @brief Represents the number of members of a record that map onto a column of a result set.
403///
404/// This is the width the record occupies in a projection built from the @c RecordColumnMember
405/// concept (e.g. @c SqlSelectQueryBuilder::Fields), and therefore the amount by which the index must be
406/// advanced to reach the first column of the record that follows it in a multi-record projection.
407/// It differs from @c RecordMemberCount exactly by the number of relation members (HasMany,
408/// HasManyThrough, HasOneThrough, ...), which have no column of their own.
409///
410/// @ingroup DataMapper
411template <typename Record>
412constexpr size_t RecordColumnCount =
413 FoldRecordMembers<Record>(size_t { 0 }, []<size_t I, typename Field>(size_t const accum) constexpr {
414 if constexpr (RecordColumnMember<Field>)
415 return accum + 1;
416 else
417 return accum;
418 });
419
420/// Represents the number of fields with storage in a record.
421///
422/// @ingroup DataMapper
423template <typename Record>
424constexpr size_t RecordStorageFieldCount =
425 FoldRecordMembers<Record>(size_t { 0 }, []<size_t I, typename Field>(size_t const accum) constexpr {
426 if constexpr (FieldWithStorage<Field>)
427 return accum + 1;
428 else
429 return accum;
430 });
431
432template <typename Record>
433concept RecordWithStorageFields = (RecordStorageFieldCount<Record> > 0);
434
435namespace detail
436{
437
438 template <auto Test, typename T>
439 constexpr bool CheckFieldProperty = FoldRecordMembers<T>(false, []<size_t I, typename Field>(bool const accum) {
440 if constexpr (Test.template operator()<Field>())
441 return true;
442 else
443 return accum;
444 });
445
446} // namespace detail
447
448/// @brief Tests if the given record type does contain a primary key.
449///
450/// @ingroup DataMapper
451template <typename T>
452constexpr bool HasPrimaryKey = detail::CheckFieldProperty<[]<typename Field>() { return IsPrimaryKey<Field>; }, T>;
453
454/// @brief Tests if the given record type does contain an auto increment primary key.
455///
456/// @ingroup DataMapper
457template <typename T>
459 detail::CheckFieldProperty<[]<typename Field>() { return IsAutoIncrementPrimaryKey<Field>; }, T>;
460
461namespace detail
462{
463 /// \cond DOXYGEN_EXCLUDE
464 // Doxygen's comment-to-declaration association misparses this decltype-of-an-invoked-generic-lambda
465 // type alias on at least one toolchain version in CI, attaching the struct's doc comment to a
466 // statement inside the lambda body instead. `detail` is excluded from the generated docs anyway
467 // (see DOXYGEN_EXCLUDE_SYMBOLS in docs/CMakeLists.txt), so there is nothing to lose by having
468 // Doxygen skip parsing it altogether.
469
470 /// Collects the value types of every `PrimaryKey` member of @p Record, in declaration order.
471 template <typename Record>
472 struct RecordPrimaryKeyTupleHelper
473 {
474 using type = decltype([]<std::size_t... I>(std::index_sequence<I...>) {
475 return std::tuple_cat([]<std::size_t J>() {
476 using FieldType = RecordMemberTypeOf<J, Record>;
477 // The two conditions must nest rather than share one `if constexpr`: `IsPrimaryKey` is
478 // not a member of the relation types (HasMany, CompositeForeignKey, ...), and `&&`
479 // inside a single condition would still instantiate the right-hand side for them.
480 if constexpr (IsField<FieldType>)
481 {
482 if constexpr (FieldType::IsPrimaryKey)
483 return std::tuple<typename FieldType::ValueType> {};
484 else
485 return std::tuple<> {};
486 }
487 else
488 return std::tuple<> {};
489 }.template operator()<I>()...);
490 }(std::make_index_sequence<RecordMemberCount<Record>> {}));
491 };
492 /// \endcond
493} // namespace detail
494
495namespace detail
496{
497 /// Number of members of @p Record declared as `PrimaryKey::AutoAssign`.
498 ///
499 /// Auto-assignment yields a single value that is then written into every primary key member, so more
500 /// than one such member cannot be honoured - see the static_assert in
501 /// `DataMapper::GenerateAutoAssignPrimaryKey`.
502 /// Whether `GenerateAutoAssignPrimaryKey` actually produces a value for @p FieldType.
503 ///
504 /// `PrimaryKey::AutoAssign` only generates for a GUID or an incrementable value; on any other type
505 /// (a string key, say) it silently generates nothing and the caller supplies the value. Only the
506 /// generating case can collide across several key members, so only it is counted.
507 template <typename ValueType>
508 concept IncrementableKeyValue = requires(ValueType value) { value + 1; };
509
510 /// Whether @p ValueType is one of the two kinds `GenerateAutoAssignPrimaryKey` actually generates a
511 /// value for: a GUID (via `SqlGuid::Create()`) or an incrementable value (via `MAX(...) + 1`).
512 template <typename ValueType>
513 concept AutoAssignableKeyValue = std::same_as<ValueType, SqlGuid> || IncrementableKeyValue<ValueType>;
514
515 template <typename FieldType>
516 concept GeneratesAutoAssignedKey = IsField<FieldType> && IsAutoAssignPrimaryKeyField<FieldType>::value
517 && AutoAssignableKeyValue<typename FieldType::ValueType>;
518
519 template <typename Record>
520 constexpr std::size_t AutoAssignPrimaryKeyFieldCount =
521 FoldRecordMembers<Record>(std::size_t { 0 }, []<std::size_t I, typename FieldType>(std::size_t const accum) {
522 if constexpr (GeneratesAutoAssignedKey<FieldType>)
523 return accum + 1;
524 else
525 return accum;
526 });
527} // namespace detail
528
529/// @brief The tuple of a record's primary key value types, in member declaration order.
530///
531/// Unlike `RecordPrimaryKeyType`, which names a single field's type, this covers composite keys:
532/// for a record with several members marked `PrimaryKey` it is a tuple of all of them. For a
533/// single-key record it is a one-element tuple.
534///
535/// Added alongside the single-key helpers rather than replacing them, so no existing caller changes
536/// behaviour; only composite-aware code reaches for this.
537///
538/// @ingroup DataMapper
539template <typename Record>
540using RecordPrimaryKeyTuple = typename detail::RecordPrimaryKeyTupleHelper<Record>::type;
541
542/// @brief Number of members of @p Record marked as a primary key.
543///
544/// One for an ordinary record, more for a composite key, zero for a keyless record.
545///
546/// @ingroup DataMapper
547template <typename Record>
548constexpr std::size_t RecordPrimaryKeyCount = std::tuple_size_v<RecordPrimaryKeyTuple<Record>>;
549
550/// @brief Whether @p Record's identity spans more than one column.
551///
552/// @ingroup DataMapper
553template <typename Record>
554constexpr bool HasCompositePrimaryKey = RecordPrimaryKeyCount<Record> > 1;
555
556/// @brief Reads every primary key value of @p record, in member declaration order.
557///
558/// This is the order a primary key lookup binds its arguments in, so the returned tuple can be applied
559/// straight to `QuerySingle`/`Update`/`Delete`.
560///
561/// @param record Record to read.
562/// @return The key values as a tuple.
563///
564/// @ingroup DataMapper
565template <typename Record>
566[[nodiscard]] RecordPrimaryKeyTuple<Record> GetPrimaryKeyFields(Record const& record)
567{
568 // Mirrors RecordPrimaryKeyTupleHelper's compile-time tuple_cat construction (one std::tuple<> or
569 // std::tuple<ValueType> per member, concatenated), but reads each primary-key member's value instead
570 // of just its type. The two therefore cannot disagree on which members are collected or in what
571 // order, and no runtime index-matching against the heterogeneous tuple is needed.
572 return []<std::size_t... I>(Record const& record, std::index_sequence<I...>) {
573 return std::tuple_cat([&record]<std::size_t J>() {
574 using FieldType = RecordMemberTypeOf<J, Record>;
575 if constexpr (IsField<FieldType>)
576 {
577 if constexpr (FieldType::IsPrimaryKey)
578 return std::tuple<typename FieldType::ValueType> { GetRecordMemberAt<J>(record).Value() };
579 else
580 return std::tuple<> {};
581 }
582 else
583 return std::tuple<> {};
584 }.template operator()<I>()...);
585 }(record, std::make_index_sequence<RecordMemberCount<Record>> {});
586}
587
588/// Returns the first primary key field of the record.
589///
590/// @ingroup DataMapper
591template <typename Record>
592inline LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType<Record> GetPrimaryKeyField(Record const& record) noexcept
593{
594 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
595 static_assert(HasPrimaryKey<Record>, "Record must have a primary key");
596
597 auto result = RecordPrimaryKeyType<Record> {};
598 bool found = false;
599 EnumerateRecordMembers(record, [&]<size_t I, typename FieldType>(FieldType const& field) {
600 // std::same_as<typename FieldType::ValueType, RecordPrimaryKeyType<Record>>condition is for the case where there are
601 // multiple primary keys, we want to return the first one
602 if constexpr (IsField<FieldType>)
603 if constexpr (IsPrimaryKey<FieldType>)
604 if constexpr (std::same_as<typename FieldType::ValueType, RecordPrimaryKeyType<Record>>)
605 if (!found)
606 {
607 result = field.Value();
608 found = true;
609 }
610 });
611 return result;
612}
613
614} // namespace Lightweight
Represents a record type that can be used with the DataMapper.
Definition Record.hpp:52
Requires that T maps onto a column of its record's table.
Definition Record.hpp:400
Constrains what may be used to single out one of several foreign keys into the same table.
Definition Record.hpp:134
LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType< Record > GetPrimaryKeyField(Record const &record) noexcept
Definition Record.hpp:592
constexpr std::size_t RecordPrimaryKeyCount
Number of members of Record marked as a primary key.
Definition Record.hpp:548
constexpr bool HasAutoIncrementPrimaryKey
Tests if the given record type does contain an auto increment primary key.
Definition Record.hpp:458
RecordPrimaryKeyTuple< Record > GetPrimaryKeyFields(Record const &record)
Reads every primary key value of record, in member declaration order.
Definition Record.hpp:566
typename detail::RecordPrimaryKeyTupleHelper< Record >::type RecordPrimaryKeyTuple
The tuple of a record's primary key value types, in member declaration order.
Definition Record.hpp:540
constexpr bool HasCompositePrimaryKey
Whether Record's identity spans more than one column.
Definition Record.hpp:554
constexpr bool IsThrough
Definition Record.hpp:181
typename detail::ThroughRecordOfHelper< ThroughSpec >::type ThroughRecordOf
Resolves the join record of a through-relationship from its template argument.
Definition Record.hpp:231
constexpr std::string_view InverseBelongsToFieldNameOf
SQL column name of the foreign key that links ChildRecord back to OwnerRecord.
Definition Record.hpp:361
std::integer_sequence< size_t, Ints... > SqlElements
Represents a sequence of indexes that can be used alongside Query() to retrieve only part of the reco...
Definition Record.hpp:25
constexpr size_t InverseBelongsToIndexOf
Member index, within ChildRecord, of the BelongsTo member that points back to OwnerRecord.
Definition Record.hpp:351
constexpr std::nullopt_t AutoDetectRelation
Selector value meaning "resolve the relationship automatically; the match must be unique".
Definition Record.hpp:119
constexpr size_t RecordStorageFieldCount
Definition Record.hpp:424
constexpr bool HasPrimaryKey
Tests if the given record type does contain a primary key.
Definition Record.hpp:452
constexpr size_t RecordColumnCount
Represents the number of members of a record that map onto a column of a result set.
Definition Record.hpp:412
constexpr void EnumerateRecordMembers(Record &record, Callable &&callable)
Invokes callable as callable<I>(member) for each member of record.
detail::RecordMemberTypeOfDispatch< I, std::remove_cvref_t< Record >, HasDescription< Record > >::type RecordMemberTypeOf
Type of the member at index I — from the descriptor if present, else via reflection.
Marks the join record of a relationship that is reached through an intermediate table.
Definition Record.hpp:158
JoinRecordT RecordType
The join record type this marker wraps.
Definition Record.hpp:160