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>();
77template <
typename Record>
78constexpr size_t RecordPrimaryKeyIndex =
79 detail::FindPrimaryKeyIndex<0, Record>().value_or((std::numeric_limits<size_t>::max)());
82template <
typename Record>
83decltype(
auto) RecordPrimaryKeyOf(Record&& record)
87 return GetRecordMemberAt<RecordPrimaryKeyIndex<std::remove_cvref_t<Record>>>(std::forward<Record>(record));
93 template <
typename Record>
94 struct RecordPrimaryKeyTypeHelper
96 using type = std::monostate;
99 template <
typename Record>
100 requires(RecordPrimaryKeyIndex<Record> < RecordMemberCount<Record>)
101 struct RecordPrimaryKeyTypeHelper<Record>
103 using type = RecordMemberTypeOf<RecordPrimaryKeyIndex<Record>, Record>::ValueType;
109template <
typename Record>
110using RecordPrimaryKeyType = details::RecordPrimaryKeyTypeHelper<Record>::type;
133template <auto Selector>
135 std::same_as<std::remove_cvref_t<
decltype(Selector)>, std::nullopt_t> ||
requires { std::string_view { Selector }; };
156template <
typename JoinRecordT>
166 template <
typename T>
167 struct IsThroughType: std::false_type
171 template <
typename JoinRecordT>
172 struct IsThroughType<Through<JoinRecordT>>: std::true_type
181constexpr bool IsThrough = detail::IsThroughType<std::remove_cvref_t<T>>::value;
193 template <
typename JoinRecordT>
194 struct BareThroughRecord
196 using type = JoinRecordT;
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;
205 template <
typename ThroughSpec>
206 struct ThroughRecordOfHelper
208 static_assert(BareThroughRecord<ThroughSpec>::DeprecatedSpelling);
209 using type =
typename BareThroughRecord<ThroughSpec>::type;
212 template <
typename JoinRecordT>
213 struct ThroughRecordOfHelper<
Through<JoinRecordT>>
215 static_assert(!IsThrough<JoinRecordT>,
216 "Through<Through<T>> is not a valid join record specification, write Through<T>.");
217 using type = JoinRecordT;
230template <
typename ThroughSpec>
242 template <auto Selector,
size_t I,
typename Record>
243 constexpr bool RelationSelectorMatches()
245 if constexpr (std::same_as<std::remove_cvref_t<
decltype(Selector)>, std::nullopt_t>)
248 return Lightweight::FieldNameAt<I, Record> == std::string_view { Selector };
252 struct InverseBelongsToLookup
261 size_t candidates {};
271 template <
typename OwnerRecord,
typename ChildRecord, auto Selector = AutoDetectRelation>
272 constexpr InverseBelongsToLookup FindInverseBelongsTo()
274 return FoldRecordMembers<ChildRecord>(
275 InverseBelongsToLookup { .index = RecordMemberCount<ChildRecord>, .count = 0, .candidates = 0 },
276 []<
size_t I,
typename MemberType>(InverseBelongsToLookup
const accum)
constexpr -> InverseBelongsToLookup {
279 if constexpr (IsBelongsTo<MemberType>)
281 if constexpr (std::same_as<typename MemberType::ReferencedRecord, OwnerRecord>)
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 };
288 return { .index = accum.index, .count = accum.count, .candidates = accum.candidates + 1 };
307 template <
typename OwnerRecord,
typename ChildRecord, auto Selector = AutoDetectRelation>
309 struct InverseBelongsToResolver
312 static constexpr InverseBelongsToLookup Lookup = FindInverseBelongsTo<OwnerRecord, ChildRecord, Selector>();
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.");
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.");
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.");
333 static constexpr size_t Index = Lookup.count == 1 ? Lookup.index : 0;
350template <
typename OwnerRecord,
typename ChildRecord, auto Selector = AutoDetectRelation>
360template <
typename OwnerRecord,
typename ChildRecord, auto Selector = AutoDetectRelation>
362 FieldNameAt<InverseBelongsToIndexOf<OwnerRecord, ChildRecord, Selector>, ChildRecord>;
365template <
typename Record,
typename TargetMappable>
366void MapFromRecordFields(Record&& record, TargetMappable& target)
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();
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>;
411template <
typename Record>
413 FoldRecordMembers<Record>(
size_t { 0 }, []<
size_t I,
typename Field>(
size_t const accum)
constexpr {
414 if constexpr (RecordColumnMember<Field>)
423template <
typename Record>
425 FoldRecordMembers<Record>(
size_t { 0 }, []<
size_t I,
typename Field>(
size_t const accum)
constexpr {
426 if constexpr (FieldWithStorage<Field>)
432template <
typename Record>
433concept RecordWithStorageFields = (RecordStorageFieldCount<Record> > 0);
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>())
452constexpr bool HasPrimaryKey = detail::CheckFieldProperty<[]<typename Field>() {
return IsPrimaryKey<Field>; }, T>;
459 detail::CheckFieldProperty<[]<typename Field>() {
return IsAutoIncrementPrimaryKey<Field>; }, T>;
471 template <
typename Record>
472 struct RecordPrimaryKeyTupleHelper
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>;
480 if constexpr (IsField<FieldType>)
482 if constexpr (FieldType::IsPrimaryKey)
483 return std::tuple<typename FieldType::ValueType> {};
485 return std::tuple<> {};
488 return std::tuple<> {};
489 }.template operator()<I>()...);
490 }(std::make_index_sequence<RecordMemberCount<Record>> {}));
507 template <
typename ValueType>
508 concept IncrementableKeyValue =
requires(ValueType value) { value + 1; };
512 template <
typename ValueType>
513 concept AutoAssignableKeyValue = std::same_as<ValueType, SqlGuid> || IncrementableKeyValue<ValueType>;
515 template <
typename FieldType>
516 concept GeneratesAutoAssignedKey = IsField<FieldType> && IsAutoAssignPrimaryKeyField<FieldType>::value
517 && AutoAssignableKeyValue<typename FieldType::ValueType>;
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>)
539template <
typename Record>
547template <
typename Record>
553template <
typename Record>
565template <
typename Record>
572 return []<std::size_t... I>(Record
const& record, std::index_sequence<I...>) {
573 return std::tuple_cat([&record]<std::size_t J>() {
575 if constexpr (IsField<FieldType>)
577 if constexpr (FieldType::IsPrimaryKey)
578 return std::tuple<typename FieldType::ValueType> { GetRecordMemberAt<J>(record).Value() };
580 return std::tuple<> {};
583 return std::tuple<> {};
584 }.template operator()<I>()...);
585 }(record, std::make_index_sequence<RecordMemberCount<Record>> {});
591template <
typename Record>
592inline LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType<Record>
GetPrimaryKeyField(Record
const& record)
noexcept
595 static_assert(HasPrimaryKey<Record>,
"Record must have a primary key");
597 auto result = RecordPrimaryKeyType<Record> {};
602 if constexpr (IsField<FieldType>)
603 if constexpr (IsPrimaryKey<FieldType>)
604 if constexpr (std::same_as<typename FieldType::ValueType, RecordPrimaryKeyType<Record>>)
607 result = field.Value();
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.
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.
typename detail::RecordPrimaryKeyTupleHelper< Record >::type RecordPrimaryKeyTuple
The tuple of a record's primary key value types, in member declaration order.
constexpr bool HasCompositePrimaryKey
Whether Record's identity spans more than one column.
typename detail::ThroughRecordOfHelper< ThroughSpec >::type ThroughRecordOf
Resolves the join record of a through-relationship from its template argument.
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
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.