Lightweight 0.20250904.0
Loading...
Searching...
No Matches
Record.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../Utils.hpp"
6#include "Field.hpp"
7
8#include <reflection-cpp/reflection.hpp>
9
10#include <concepts>
11#include <limits>
12
13namespace Lightweight
14{
15
16/// @brief Represents a sequence of indexes that can be used alongside Query() to retrieve only part of the record.
17///
18/// @ingroup DataMapper
19template <size_t... Ints>
20using SqlElements = std::integer_sequence<size_t, Ints...>;
21
22namespace detail
23{
24 // Helper trait to detect specializations of SqlElements
25 template <typename T>
26 struct IsSqlElements: std::false_type
27 {
28 };
29
30 template <size_t... Ints>
31 struct IsSqlElements<SqlElements<Ints...>>: std::true_type
32 {
33 };
34} // namespace detail
35
36// @brief Helper concept to check if a type is not a specialization of SqlElements
37template <typename T>
38concept NotSqlElements = !detail::IsSqlElements<T>::value;
39
40/// @brief Represents a record type that can be used with the DataMapper.
41///
42/// The record type must be an aggregate type.
43///
44/// @see DataMapper, Field, BelongsTo, HasMany, HasManyThrough, HasOneThrough
45/// @ingroup DataMapper
46template <typename Record>
47concept DataMapperRecord = std::is_aggregate_v<Record> && NotSqlElements<Record>;
48
49template <typename... Records>
50concept DataMapperRecords = (DataMapperRecord<Records> && ...);
51
52namespace detail
53{
54
55 template <std::size_t I, typename Record>
56 constexpr std::optional<size_t> FindPrimaryKeyIndex()
57 {
58 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
59 if constexpr (I < Reflection::CountMembers<Record>)
60 {
61 if constexpr (IsPrimaryKey<Reflection::MemberTypeOf<I, Record>>)
62 return { I };
63 else
64 return FindPrimaryKeyIndex<I + 1, Record>();
65 }
66 return std::nullopt;
67 }
68
69} // namespace detail
70
71/// Declare RecordPrimaryKeyIndex<Record> to retrieve the primary key index of the given record.
72template <typename Record>
73constexpr size_t RecordPrimaryKeyIndex =
74 detail::FindPrimaryKeyIndex<0, Record>().value_or((std::numeric_limits<size_t>::max)());
75
76/// Retrieves a reference to the given record's primary key.
77template <typename Record>
78decltype(auto) RecordPrimaryKeyOf(Record&& record)
79{
80 // static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
81 // static_assert(RecordPrimaryKeyIndex<Record> != static_cast<size_t>(-1), "Record must have a primary key");
82 return Reflection::GetMemberAt<RecordPrimaryKeyIndex<std::remove_cvref_t<Record>>>(std::forward<Record>(record));
83}
84
85namespace details
86{
87
88 template <typename Record>
89 struct RecordPrimaryKeyTypeHelper
90 {
91 using type = void;
92 };
93
94 template <typename Record>
95 requires(RecordPrimaryKeyIndex<Record> < Reflection::CountMembers<Record>)
96 struct RecordPrimaryKeyTypeHelper<Record>
97 {
98 using type = typename Reflection::MemberTypeOf<RecordPrimaryKeyIndex<Record>, Record>::ValueType;
99 };
100
101} // namespace details
102
103/// Reflects the primary key type of the given record.
104template <typename Record>
105using RecordPrimaryKeyType = typename details::RecordPrimaryKeyTypeHelper<Record>::type;
106
107/// @brief Maps the fields of the given record to the target that supports the operator[].
108template <typename Record, typename TargetMappable>
109void MapFromRecordFields(Record&& record, TargetMappable& target)
110{
111 Reflection::EnumerateMembers(std::forward<Record>(record), [&]<std::size_t I>(auto const& field) {
112 using MemberType = Reflection::MemberTypeOf<I, Record>;
113 static_assert(IsField<MemberType>, "Record member must be a Field<> type");
114 static_assert(std::is_assignable_v<decltype(target[I]), decltype(field.Value())>,
115 "Target must support operator[] with the field type");
116 target[I] = field.Value();
117 });
118}
119
120/// Requires that T satisfies to be a field with storage.
121///
122/// @ingroup DataMapper
123template <typename T>
124concept FieldWithStorage = requires(T const& field, T& mutableField) {
125 // clang-format off
126 { field.Value() } -> std::convertible_to<typename T::ValueType const&>;
127 { mutableField.MutableValue() } -> std::convertible_to<typename T::ValueType&>;
128 { field.IsModified() } -> std::convertible_to<bool>;
129 { mutableField.SetModified(bool {}) } -> std::convertible_to<void>;
130 // clang-format on
131};
132
133/// Represents the number of fields with storage in a record.
134///
135/// @ingroup DataMapper
136template <typename Record>
137constexpr size_t RecordStorageFieldCount =
138 Reflection::FoldMembers<Record>(size_t { 0 }, []<size_t I, typename Field>(size_t const accum) constexpr {
139 if constexpr (FieldWithStorage<Field>)
140 return accum + 1;
141 else
142 return accum;
143 });
144
145template <typename Record>
146concept RecordWithStorageFields = (RecordStorageFieldCount<Record> > 0);
147
148namespace detail
149{
150
151 template <auto Test, typename T>
152 constexpr bool CheckFieldProperty = Reflection::FoldMembers<T>(false, []<size_t I, typename Field>(bool const accum) {
153 if constexpr (Test.template operator()<Field>())
154 return true;
155 else
156 return accum;
157 });
158
159} // namespace detail
160
161/// @brief Tests if the given record type does contain a primary key.
162///
163/// @ingroup DataMapper
164template <typename T>
165constexpr bool HasPrimaryKey = detail::CheckFieldProperty<[]<typename Field>() { return IsPrimaryKey<Field>; }, T>;
166
167/// @brief Tests if the given record type does contain an auto increment primary key.
168///
169/// @ingroup DataMapper
170template <typename T>
172 detail::CheckFieldProperty<[]<typename Field>() { return IsAutoIncrementPrimaryKey<Field>; }, T>;
173
174/// Returns the first primary key field of the record.
175///
176/// @ingroup DataMapper
177template <typename Record>
178inline LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType<Record> GetPrimaryKeyField(Record const& record) noexcept
179{
180 static_assert(DataMapperRecord<Record>, "Record must satisfy DataMapperRecord");
181 static_assert(HasPrimaryKey<Record>, "Record must have a primary key");
182
183 auto result = RecordPrimaryKeyType<Record> {};
184 Reflection::EnumerateMembers(record, [&]<size_t I, typename FieldType>(FieldType const& field) {
185 if constexpr (IsPrimaryKey<FieldType> && std::same_as<FieldType, RecordPrimaryKeyType<Record>>)
186 {
187 result = field;
188 }
189 });
190 return result;
191}
192
193} // namespace Lightweight
Represents a record type that can be used with the DataMapper.
Definition Record.hpp:47
LIGHTWEIGHT_FORCE_INLINE RecordPrimaryKeyType< Record > GetPrimaryKeyField(Record const &record) noexcept
Definition Record.hpp:178
constexpr bool HasAutoIncrementPrimaryKey
Tests if the given record type does contain an auto increment primary key.
Definition Record.hpp:171
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:20
constexpr size_t RecordStorageFieldCount
Definition Record.hpp:137
constexpr bool HasPrimaryKey
Tests if the given record type does contain a primary key.
Definition Record.hpp:165