5#include "../DataBinder/SqlGuid.hpp"
7#include "BelongsTo.hpp"
10#include <reflection-cpp/reflection.hpp>
24template <
size_t... Ints>
31 struct IsSqlElements: std::false_type
35 template <
size_t... Ints>
36 struct IsSqlElements<
SqlElements<Ints...>>: std::true_type
43concept NotSqlElements = !detail::IsSqlElements<T>::value;
51template <
typename Record>
54template <
typename... Records>
60 template <std::
size_t I,
typename Record>
61 constexpr std::optional<size_t> FindPrimaryKeyIndex()
64 if constexpr (I < RecordMemberCount<Record>)
66 if constexpr (IsPrimaryKey<RecordMemberTypeOf<I, Record>>)
69 return FindPrimaryKeyIndex<I + 1, Record>();
76 template <std::
size_t I,
typename Record>
77 constexpr std::optional<size_t> FindGeneratedPrimaryKeyIndex()
79 if constexpr (I < RecordMemberCount<Record>)
81 using FieldType = RecordMemberTypeOf<I, Record>;
82 if constexpr (IsAutoAssignPrimaryKeyField<FieldType>::value || IsAutoIncrementPrimaryKeyField<FieldType>::value)
85 return FindGeneratedPrimaryKeyIndex<I + 1, Record>();
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)()));
101template <
typename Record>
102decltype(
auto) RecordPrimaryKeyOf(Record&& record)
106 return GetRecordMemberAt<RecordPrimaryKeyIndex<std::remove_cvref_t<Record>>>(std::forward<Record>(record));
112 template <
typename Record>
113 struct RecordPrimaryKeyTypeHelper
115 using type = std::monostate;
118 template <
typename Record>
119 requires(RecordPrimaryKeyIndex<Record> < RecordMemberCount<Record>)
120 struct RecordPrimaryKeyTypeHelper<Record>
122 using type = RecordMemberTypeOf<RecordPrimaryKeyIndex<Record>, Record>::ValueType;
128template <
typename Record>
129using RecordPrimaryKeyType = details::RecordPrimaryKeyTypeHelper<Record>::type;
152template <auto Selector>
154 std::same_as<std::remove_cvref_t<
decltype(Selector)>, std::nullopt_t> ||
requires { std::string_view { Selector }; };
175template <
typename JoinRecordT>
185 template <
typename T>
186 struct IsThroughType: std::false_type
190 template <
typename JoinRecordT>
191 struct IsThroughType<Through<JoinRecordT>>: std::true_type
200constexpr bool IsThrough = detail::IsThroughType<std::remove_cvref_t<T>>::value;
212 template <
typename JoinRecordT>
213 struct BareThroughRecord
215 using type = JoinRecordT;
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;
224 template <
typename ThroughSpec>
225 struct ThroughRecordOfHelper
227 static_assert(BareThroughRecord<ThroughSpec>::DeprecatedSpelling);
228 using type = BareThroughRecord<ThroughSpec>::type;
231 template <
typename JoinRecordT>
232 struct ThroughRecordOfHelper<
Through<JoinRecordT>>
234 static_assert(!IsThrough<JoinRecordT>,
235 "Through<Through<T>> is not a valid join record specification, write Through<T>.");
236 using type = JoinRecordT;
249template <
typename ThroughSpec>
261 template <auto Selector,
size_t I,
typename Record>
262 constexpr bool RelationSelectorMatches()
264 if constexpr (std::same_as<std::remove_cvref_t<
decltype(Selector)>, std::nullopt_t>)
267 return Lightweight::FieldNameAt<I, Record> == std::string_view { Selector };
271 struct InverseBelongsToLookup
280 size_t candidates {};
290 template <
typename OwnerRecord,
typename ChildRecord, auto Selector = AutoDetectRelation>
291 constexpr InverseBelongsToLookup FindInverseBelongsTo()
293 return FoldRecordMembers<ChildRecord>(
294 InverseBelongsToLookup { .index = RecordMemberCount<ChildRecord>, .count = 0, .candidates = 0 },
295 []<
size_t I,
typename MemberType>(InverseBelongsToLookup
const accum)
constexpr -> InverseBelongsToLookup {
298 if constexpr (IsBelongsTo<MemberType>)
300 if constexpr (std::same_as<typename MemberType::ReferencedRecord, OwnerRecord>)
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 };
307 return { .index = accum.index, .count = accum.count, .candidates = accum.candidates + 1 };
326 template <
typename OwnerRecord,
typename ChildRecord, auto Selector = AutoDetectRelation>
328 struct InverseBelongsToResolver
331 static constexpr InverseBelongsToLookup Lookup = FindInverseBelongsTo<OwnerRecord, ChildRecord, Selector>();
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.");
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.");
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.");
352 static constexpr size_t Index = Lookup.count == 1 ? Lookup.index : 0;
369template <
typename OwnerRecord,
typename ChildRecord, auto Selector = AutoDetectRelation>
379template <
typename OwnerRecord,
typename ChildRecord, auto Selector = AutoDetectRelation>
381 FieldNameAt<InverseBelongsToIndexOf<OwnerRecord, ChildRecord, Selector>, ChildRecord>;
384template <
typename Record,
typename TargetMappable>
385void MapFromRecordFields(Record&& record, TargetMappable& target)
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();
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>;
430template <
typename Record>
432 FoldRecordMembers<Record>(
size_t { 0 }, []<
size_t I,
typename Field>(
size_t const accum)
constexpr {
433 if constexpr (RecordColumnMember<Field>)
442template <
typename Record>
444 FoldRecordMembers<Record>(
size_t { 0 }, []<
size_t I,
typename Field>(
size_t const accum)
constexpr {
445 if constexpr (FieldWithStorage<Field>)
451template <
typename Record>
452concept RecordWithStorageFields = (RecordStorageFieldCount<Record> > 0);
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>())
471constexpr bool HasPrimaryKey = detail::CheckFieldProperty<[]<typename Field>() {
return IsPrimaryKey<Field>; }, T>;
478 detail::CheckFieldProperty<[]<typename Field>() {
return IsAutoIncrementPrimaryKey<Field>; }, T>;
490 template <
typename Record>
491 struct RecordPrimaryKeyTupleHelper
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>;
499 if constexpr (IsField<FieldType>)
501 if constexpr (FieldType::IsPrimaryKey)
502 return std::tuple<typename FieldType::ValueType> {};
504 return std::tuple<> {};
507 return std::tuple<> {};
508 }.template operator()<I>()...);
509 }(std::make_index_sequence<RecordMemberCount<Record>> {}));
526 template <
typename ValueType>
527 concept IncrementableKeyValue =
requires(ValueType value) { value + 1; };
531 template <
typename ValueType>
532 concept AutoAssignableKeyValue = std::same_as<ValueType, SqlGuid> || IncrementableKeyValue<ValueType>;
534 template <
typename FieldType>
535 concept GeneratesAutoAssignedKey = IsField<FieldType> && IsAutoAssignPrimaryKeyField<FieldType>::value
536 && AutoAssignableKeyValue<typename FieldType::ValueType>;
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>)
558template <
typename Record>
566template <
typename Record>
572template <
typename Record>
584template <
typename Record>
591 return []<std::size_t... I>(Record
const& record, std::index_sequence<I...>) {
592 return std::tuple_cat([&record]<std::size_t J>() {
594 if constexpr (IsField<FieldType>)
596 if constexpr (FieldType::IsPrimaryKey)
597 return std::tuple<typename FieldType::ValueType> { GetRecordMemberAt<J>(record).Value() };
599 return std::tuple<> {};
602 return std::tuple<> {};
603 }.template operator()<I>()...);
604 }(record, std::make_index_sequence<RecordMemberCount<Record>> {});
614template <
typename Record>
615inline LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType<Record>
GetPrimaryKeyField(Record
const& record)
noexcept
618 static_assert(HasPrimaryKey<Record>,
"Record must have a primary key");
620 auto result = RecordPrimaryKeyType<Record> {};
622 bool foundGenerated =
false;
626 if constexpr (IsField<FieldType> && IsPrimaryKey<FieldType>)
628 if constexpr (std::same_as<typename FieldType::ValueType, RecordPrimaryKeyType<Record>>)
630 constexpr bool Generated = FieldType::IsAutoAssignPrimaryKey || FieldType::IsAutoIncrementPrimaryKey;
631 if (!found || (Generated && !foundGenerated))
633 result = field.Value();
635 foundGenerated = Generated;
Represents a record type that can be used with the DataMapper.
Requires that T maps onto a column of its record's table.
Constrains what may be used to single out one of several foreign keys into the same table.
LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType< Record > GetPrimaryKeyField(Record const &record) noexcept
constexpr std::size_t RecordPrimaryKeyCount
Number of members of Record marked as a primary key.
detail::ThroughRecordOfHelper< ThroughSpec >::type ThroughRecordOf
Resolves the join record of a through-relationship from its template argument.
constexpr bool HasAutoIncrementPrimaryKey
Tests if the given record type does contain an auto increment primary key.
RecordPrimaryKeyTuple< Record > GetPrimaryKeyFields(Record const &record)
Reads every primary key value of record, in member declaration order.
constexpr bool HasCompositePrimaryKey
Whether Record's identity spans more than one column.
constexpr std::string_view InverseBelongsToFieldNameOf
SQL column name of the foreign key that links ChildRecord back to OwnerRecord.
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...
constexpr size_t InverseBelongsToIndexOf
Member index, within ChildRecord, of the BelongsTo member that points back to OwnerRecord.
constexpr std::nullopt_t AutoDetectRelation
Selector value meaning "resolve the relationship automatically; the match must be unique".
constexpr size_t RecordStorageFieldCount
detail::RecordPrimaryKeyTupleHelper< Record >::type RecordPrimaryKeyTuple
The tuple of a record's primary key value types, in member declaration order.
constexpr bool HasPrimaryKey
Tests if the given record type does contain a primary key.
constexpr size_t RecordColumnCount
Represents the number of members of a record that map onto a column of a result set.
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.
JoinRecordT RecordType
The join record type this marker wraps.