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