Lightweight 0.20260625.0
Loading...
Searching...
No Matches
HasMany.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../DataBinder/Core.hpp"
6#include "../DataBinder/SqlNullValue.hpp"
7#include "../SqlStatement.hpp"
8#include "BelongsTo.hpp"
9#include "Field.hpp"
10#include "Record.hpp"
11
12#include <reflection-cpp/reflection.hpp>
13
14#include <compare>
15#include <memory>
16#include <optional>
17#include <type_traits>
18#include <vector>
19
20namespace Lightweight
21{
22
23/// @brief This HasMany<OtherRecord> represents a simple one-to-many relationship between two records.
24///
25/// The HasMany<OtherRecord> is a member of the "one" side of the relationship.
26///
27/// `OtherRecord` must declare a `BelongsTo` member that points back to this "one" side. That member is
28/// located by matching the relationship *type*, not by its position in either record, so the two
29/// relationship members may be declared at any index. Declaring no such `BelongsTo` is a compile-time
30/// error.
31///
32/// When `OtherRecord` holds more than one foreign key into this record's table - say a meeting that
33/// references the same person table both as its organizer and as whoever writes the minutes - the
34/// inverse is ambiguous. Name the foreign key column through @p TheInverseSelector to single one out:
35///
36/// @code
37/// struct Meeting;
38/// struct Human
39/// {
40/// Field<int, PrimaryKey::AutoAssign> id;
41/// HasMany<Meeting, SqlRealName { "organizer_id" }> organizedMeetings;
42/// HasMany<Meeting, SqlRealName { "minute_taker_id" }> minutedMeetings;
43/// };
44/// struct Meeting
45/// {
46/// Field<int, PrimaryKey::AutoAssign> id;
47/// BelongsTo<&Human::id, SqlRealName { "organizer_id" }> organizer;
48/// BelongsTo<&Human::id, SqlRealName { "minute_taker_id" }, SqlNullable::Null> minuteTaker;
49/// };
50/// @endcode
51///
52/// A meeting with *many* attendees is a many-to-many instead - see `HasManyThrough`. The worked
53/// example in `docs/sql-to-lightweight.md` combines both shapes.
54///
55/// @tparam OtherRecord The record type on the "many" side of the relationship.
56/// @tparam TheInverseSelector Singles out one of several foreign keys, see the RelationSelector concept.
57///
58/// @see InverseBelongsToIndexOf, RelationSelector
59///
60/// @see DataMapper, Field, HasManyThrough
61/// @ingroup DataMapper
62template <typename OtherRecord, auto TheInverseSelector = AutoDetectRelation>
64{
66 "The second template argument of HasMany must be a foreign key column name (a SqlRealName) "
67 "or std::nullopt to resolve the relationship automatically.");
68
69 public:
70 /// The record type of the "many" side of the relationship.
71 using ReferencedRecord = OtherRecord;
72
73 /// Singles out the foreign key of `OtherRecord` that backs this relationship.
74 static constexpr auto InverseSelector = TheInverseSelector;
75
76 /// The list of records on the "many" side of the relationship.
77 using ReferencedRecordList = std::vector<std::shared_ptr<OtherRecord>>;
78
79 /// Record type of the "many" side of the relationship.
80 using value_type = OtherRecord;
81
82 /// Iterator type for the list of records.
83 using iterator = ReferencedRecordList::iterator;
84
85 /// Const iterator type for the list of records.
86 using const_iterator = ReferencedRecordList::const_iterator;
87
88 /// Retrieves the list of loaded records.
89 [[nodiscard]] ReferencedRecordList const& All() const noexcept;
90
91 /// Retrieves the list of records as mutable reference.
92 [[nodiscard]] ReferencedRecordList& All() noexcept;
93
94 /// @brief Iterates over the list of records and calls the given callable for each record.
95 ///
96 /// @note Use this method if you want to iterate over all records but do not need to store them all in memory, e.g.
97 /// because the full data set wuold be too large.
98 template <typename Callable>
99 void Each(Callable const& callable);
100
101 /// Emplaces the given list of records.
103
104 /// Retrieves the number of records in this 1-to-many relationship.
105 [[nodiscard]] std::size_t Count() const noexcept;
106
107 /// Checks if this 1-to-many relationship is empty.
108 [[nodiscard]] bool IsEmpty() const noexcept;
109
110 /// @brief Retrieves the record at the given index.
111 ///
112 /// @param index The index of the record to retrieve.
113 /// @note This method will on-demand load the records if they are not already loaded.
114 /// @note This method will throw if the index is out of bounds.
115 [[nodiscard]] OtherRecord const& At(std::size_t index) const;
116
117 /// @brief Retrieves the record at the given index.
118 ///
119 /// @param index The index of the record to retrieve.
120 /// @note This method will on-demand load the records if they are not already loaded.
121 /// @note This method will throw if the index is out of bounds.
122 [[nodiscard]] OtherRecord& At(std::size_t index);
123
124 /// @brief Retrieves the record at the given index.
125 ///
126 /// @param index The index of the record to retrieve.
127 /// @note This method will on-demand load the records if they are not already loaded.
128 /// @note This method will NOT throw if the index is out of bounds. The behaviour is undefined.
129 [[nodiscard]] OtherRecord const& operator[](std::size_t index) const;
130
131 /// @brief Retrieves the record at the given index.
132 ///
133 /// @param index The index of the record to retrieve.
134 /// @note This method will on-demand load the records if they are not already loaded.
135 /// @note This method will NOT throw if the index is out of bounds. The behaviour is undefined.
136 [[nodiscard]] OtherRecord& operator[](std::size_t index);
137
138 /// Returns an iterator to the beginning of the record list.
139 [[nodiscard]] iterator begin() noexcept;
140 /// Returns an iterator to the end of the record list.
141 [[nodiscard]] iterator end() noexcept;
142 /// Returns a const iterator to the beginning of the record list.
143 [[nodiscard]] const_iterator begin() const noexcept;
144 /// Returns a const iterator to the end of the record list.
145 [[nodiscard]] const_iterator end() const noexcept;
146
147 /// Three-way comparison operator.
148 constexpr std::weak_ordering operator<=>(HasMany const& other) const noexcept = default;
149 /// Equality comparison operator.
150 constexpr bool operator==(HasMany const& other) const noexcept = default;
151 /// Inequality comparison operator.
152 constexpr bool operator!=(HasMany const& other) const noexcept = default;
153
154 struct Loader
155 {
156 std::function<size_t()> count {};
157 std::function<ReferencedRecordList()> all {};
158 std::function<void(std::function<void(ReferencedRecord const&)>)> each {};
159
160 std::weak_ordering operator<=>(Loader const& /*other*/) const noexcept
161 {
162 return std::weak_ordering::equivalent; // Loader is not comparable, so we return equivalent
163 }
164 };
165
166 /// Used internally to configure on-demand loading of the records.
167 void SetAutoLoader(Loader loader) noexcept;
168
169 private:
170 void RequireLoaded();
171
172 Loader _loader;
173 std::optional<ReferencedRecordList> _records;
174 std::optional<size_t> _count;
175};
176
177namespace detail
178{
179 template <typename T>
180 struct IsHasManyType: std::false_type
181 {
182 };
183
184 template <typename OtherRecord, auto InverseSelector>
185 struct IsHasManyType<HasMany<OtherRecord, InverseSelector>>: std::true_type
186 {
187 };
188
189} // namespace detail
190
191template <typename T>
192constexpr bool IsHasMany = detail::IsHasManyType<std::remove_cvref_t<T>>::value;
193
194template <typename OtherRecord, auto InverseSelector>
195inline LIGHTWEIGHT_FORCE_INLINE void HasMany<OtherRecord, InverseSelector>::SetAutoLoader(Loader loader) noexcept
196{
197 _loader = std::move(loader);
198}
199
200template <typename OtherRecord, auto InverseSelector>
201inline LIGHTWEIGHT_FORCE_INLINE void HasMany<OtherRecord, InverseSelector>::RequireLoaded()
202{
203 if (!_records)
204 _records = _loader.all();
205}
206
207template <typename OtherRecord, auto InverseSelector>
208inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord, InverseSelector>::ReferencedRecordList& HasMany<
209 OtherRecord,
210 InverseSelector>::Emplace(ReferencedRecordList&& records) noexcept
211{
212 _records = { std::move(records) };
213 return *_records;
214}
215
216template <typename OtherRecord, auto InverseSelector>
218 OtherRecord,
219 InverseSelector>::All() noexcept
220{
221 RequireLoaded();
222 return *_records; // NOLINT(bugprone-unchecked-optional-access)
223}
224
225template <typename OtherRecord, auto InverseSelector>
226template <typename Callable>
228{
229 if (!_records && _loader.each)
230 {
231 _loader.each(callable);
232 return;
233 }
234
235 for (auto const& record: All())
236 callable(*record);
237}
238
239template <typename OtherRecord, auto InverseSelector>
240inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord, InverseSelector>::ReferencedRecordList const& HasMany<
241 OtherRecord,
242 InverseSelector>::All() const noexcept
243{
244 const_cast<HasMany*>(this)->RequireLoaded();
245 return *_records;
246}
247
248template <typename OtherRecord, auto InverseSelector>
249inline LIGHTWEIGHT_FORCE_INLINE std::size_t HasMany<OtherRecord, InverseSelector>::Count() const noexcept
250{
251 if (_records)
252 return _records->size();
253
254 if (!_count && _loader.count)
255 const_cast<HasMany<OtherRecord, InverseSelector>*>(this)->_count = _loader.count();
256
257 return _count.value_or(0);
258}
259
260template <typename OtherRecord, auto InverseSelector>
261inline LIGHTWEIGHT_FORCE_INLINE bool HasMany<OtherRecord, InverseSelector>::IsEmpty() const noexcept
262{
263 return Count() == 0;
264}
265
266template <typename OtherRecord, auto InverseSelector>
267inline LIGHTWEIGHT_FORCE_INLINE OtherRecord const& HasMany<OtherRecord, InverseSelector>::At(std::size_t index) const
268{
269 const_cast<HasMany*>(this)->RequireLoaded();
270 return *_records->at(index); // NOLINT(bugprone-unchecked-optional-access)
271}
272
273template <typename OtherRecord, auto InverseSelector>
274inline LIGHTWEIGHT_FORCE_INLINE OtherRecord& HasMany<OtherRecord, InverseSelector>::At(std::size_t index)
275{
276 RequireLoaded();
277 return *_records->at(index); // NOLINT(bugprone-unchecked-optional-access)
278}
279
280template <typename OtherRecord, auto InverseSelector>
281inline LIGHTWEIGHT_FORCE_INLINE OtherRecord const& HasMany<OtherRecord, InverseSelector>::operator[](std::size_t index) const
282{
283 const_cast<HasMany*>(this)->RequireLoaded();
284 return *(*_records)[index]; // NOLINT(bugprone-unchecked-optional-access)
285}
286
287template <typename OtherRecord, auto InverseSelector>
288inline LIGHTWEIGHT_FORCE_INLINE OtherRecord& HasMany<OtherRecord, InverseSelector>::operator[](std::size_t index)
289{
290 RequireLoaded();
291 return *(*_records)[index]; // NOLINT(bugprone-unchecked-optional-access)
292}
293
294template <typename OtherRecord, auto InverseSelector>
295inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord, InverseSelector>::iterator HasMany<OtherRecord,
296 InverseSelector>::begin() noexcept
297{
298 RequireLoaded();
299 if (_records)
300 return _records->begin();
301 else
302 return iterator {};
303}
304
305template <typename OtherRecord, auto InverseSelector>
306inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord, InverseSelector>::iterator HasMany<OtherRecord,
307 InverseSelector>::end() noexcept
308{
309 RequireLoaded();
310 if (_records)
311 return _records->end();
312 else
313 return iterator {};
314}
315
316template <typename OtherRecord, auto InverseSelector>
317inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord, InverseSelector>::const_iterator HasMany<OtherRecord,
318 InverseSelector>::begin()
319 const noexcept
320{
321 const_cast<HasMany*>(this)->RequireLoaded();
322 if (_records)
323 return _records->begin();
324 else
325 return const_iterator {};
326}
327
328template <typename OtherRecord, auto InverseSelector>
329inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord, InverseSelector>::const_iterator HasMany<OtherRecord,
330 InverseSelector>::end()
331 const noexcept
332{
333 const_cast<HasMany*>(this)->RequireLoaded();
334 if (_records)
335 return _records->end();
336 else
337 return const_iterator {};
338}
339
340} // namespace Lightweight
This HasMany<OtherRecord> represents a simple one-to-many relationship between two records.
Definition HasMany.hpp:64
OtherRecord const & At(std::size_t index) const
Retrieves the record at the given index.
Definition HasMany.hpp:267
iterator end() noexcept
Returns an iterator to the end of the record list.
Definition HasMany.hpp:307
iterator begin() noexcept
Returns an iterator to the beginning of the record list.
Definition HasMany.hpp:296
ReferencedRecordList::iterator iterator
Iterator type for the list of records.
Definition HasMany.hpp:83
void Each(Callable const &callable)
Iterates over the list of records and calls the given callable for each record.
Definition HasMany.hpp:227
constexpr std::weak_ordering operator<=>(HasMany const &other) const noexcept=default
Three-way comparison operator.
ReferencedRecordList::const_iterator const_iterator
Const iterator type for the list of records.
Definition HasMany.hpp:86
OtherRecord ReferencedRecord
The record type of the "many" side of the relationship.
Definition HasMany.hpp:71
std::size_t Count() const noexcept
Retrieves the number of records in this 1-to-many relationship.
Definition HasMany.hpp:249
ReferencedRecordList const & All() const noexcept
Retrieves the list of loaded records.
Definition HasMany.hpp:242
OtherRecord const & operator[](std::size_t index) const
Retrieves the record at the given index.
Definition HasMany.hpp:281
OtherRecord value_type
Record type of the "many" side of the relationship.
Definition HasMany.hpp:80
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the records.
Definition HasMany.hpp:195
static constexpr auto InverseSelector
Singles out the foreign key of OtherRecord that backs this relationship.
Definition HasMany.hpp:74
std::vector< std::shared_ptr< OtherRecord > > ReferencedRecordList
The list of records on the "many" side of the relationship.
Definition HasMany.hpp:77
bool IsEmpty() const noexcept
Checks if this 1-to-many relationship is empty.
Definition HasMany.hpp:261
ReferencedRecordList & Emplace(ReferencedRecordList &&records) noexcept
Emplaces the given list of records.
Definition HasMany.hpp:210
Constrains what may be used to single out one of several foreign keys into the same table.
Definition Record.hpp:132