Lightweight 0.20260303.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/// This implemenation of `HasMany<OtherRecord>` must have only one `BelongsTo` member
28/// that points back to this "one" side.
29///
30/// @see DataMapper, Field, HasManyThrough
31/// @ingroup DataMapper
32template <typename OtherRecord>
34{
35 public:
36 /// The record type of the "many" side of the relationship.
37 using ReferencedRecord = OtherRecord;
38
39 /// The list of records on the "many" side of the relationship.
40 using ReferencedRecordList = std::vector<std::shared_ptr<OtherRecord>>;
41
42 /// Record type of the "many" side of the relationship.
43 using value_type = OtherRecord;
44
45 /// Iterator type for the list of records.
46 using iterator = ReferencedRecordList::iterator;
47
48 /// Const iterator type for the list of records.
49 using const_iterator = ReferencedRecordList::const_iterator;
50
51 /// Retrieves the list of loaded records.
52 [[nodiscard]] ReferencedRecordList const& All() const noexcept;
53
54 /// Retrieves the list of records as mutable reference.
55 [[nodiscard]] ReferencedRecordList& All() noexcept;
56
57 /// @brief Iterates over the list of records and calls the given callable for each record.
58 ///
59 /// @note Use this method if you want to iterate over all records but do not need to store them all in memory, e.g.
60 /// because the full data set wuold be too large.
61 template <typename Callable>
62 void Each(Callable const& callable);
63
64 /// Emplaces the given list of records.
66
67 /// Retrieves the number of records in this 1-to-many relationship.
68 [[nodiscard]] std::size_t Count() const noexcept;
69
70 /// Checks if this 1-to-many relationship is empty.
71 [[nodiscard]] bool IsEmpty() const noexcept;
72
73 /// @brief Retrieves the record at the given index.
74 ///
75 /// @param index The index of the record to retrieve.
76 /// @note This method will on-demand load the records if they are not already loaded.
77 /// @note This method will throw if the index is out of bounds.
78 [[nodiscard]] OtherRecord const& At(std::size_t index) const;
79
80 /// @brief Retrieves the record at the given index.
81 ///
82 /// @param index The index of the record to retrieve.
83 /// @note This method will on-demand load the records if they are not already loaded.
84 /// @note This method will throw if the index is out of bounds.
85 [[nodiscard]] OtherRecord& At(std::size_t index);
86
87 /// @brief Retrieves the record at the given index.
88 ///
89 /// @param index The index of the record to retrieve.
90 /// @note This method will on-demand load the records if they are not already loaded.
91 /// @note This method will NOT throw if the index is out of bounds. The behaviour is undefined.
92 [[nodiscard]] OtherRecord const& operator[](std::size_t index) const;
93
94 /// @brief Retrieves the record at the given index.
95 ///
96 /// @param index The index of the record to retrieve.
97 /// @note This method will on-demand load the records if they are not already loaded.
98 /// @note This method will NOT throw if the index is out of bounds. The behaviour is undefined.
99 [[nodiscard]] OtherRecord& operator[](std::size_t index);
100
101 /// Returns an iterator to the beginning of the record list.
102 [[nodiscard]] iterator begin() noexcept;
103 /// Returns an iterator to the end of the record list.
104 [[nodiscard]] iterator end() noexcept;
105 /// Returns a const iterator to the beginning of the record list.
106 [[nodiscard]] const_iterator begin() const noexcept;
107 /// Returns a const iterator to the end of the record list.
108 [[nodiscard]] const_iterator end() const noexcept;
109
110 /// Three-way comparison operator.
111 constexpr std::weak_ordering operator<=>(HasMany const& other) const noexcept = default;
112 /// Equality comparison operator.
113 constexpr bool operator==(HasMany const& other) const noexcept = default;
114 /// Inequality comparison operator.
115 constexpr bool operator!=(HasMany const& other) const noexcept = default;
116
117 struct Loader
118 {
119 std::function<size_t()> count {};
120 std::function<ReferencedRecordList()> all {};
121 std::function<void(std::function<void(ReferencedRecord const&)>)> each {};
122
123 std::weak_ordering operator<=>(Loader const& /*other*/) const noexcept
124 {
125 return std::weak_ordering::equivalent; // Loader is not comparable, so we return equivalent
126 }
127 };
128
129 /// Used internally to configure on-demand loading of the records.
130 void SetAutoLoader(Loader loader) noexcept;
131
132 private:
133 void RequireLoaded();
134
135 Loader _loader;
136 std::optional<ReferencedRecordList> _records;
137 std::optional<size_t> _count;
138};
139
140template <typename T>
141constexpr bool IsHasMany = IsSpecializationOf<HasMany, T>;
142
143template <typename OtherRecord>
144inline LIGHTWEIGHT_FORCE_INLINE void HasMany<OtherRecord>::SetAutoLoader(Loader loader) noexcept
145{
146 _loader = std::move(loader);
147}
148
149template <typename OtherRecord>
150inline LIGHTWEIGHT_FORCE_INLINE void HasMany<OtherRecord>::RequireLoaded()
151{
152 if (!_records)
153 _records = _loader.all();
154}
155
156template <typename OtherRecord>
158 ReferencedRecordList&& records) noexcept
159{
160 _records = { std::move(records) };
161 return *_records;
162}
163
164template <typename OtherRecord>
166{
167 RequireLoaded();
168 return *_records; // NOLINT(bugprone-unchecked-optional-access)
169}
170
171template <typename OtherRecord>
172template <typename Callable>
173void HasMany<OtherRecord>::Each(Callable const& callable)
174{
175 if (!_records && _loader.each)
176 {
177 _loader.each(callable);
178 return;
179 }
180
181 for (auto const& record: All())
182 callable(*record);
183}
184
185template <typename OtherRecord>
186inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::ReferencedRecordList const& HasMany<OtherRecord>::All() const noexcept
187{
188 RequireLoaded();
189 return *_records;
190}
191
192template <typename OtherRecord>
193inline LIGHTWEIGHT_FORCE_INLINE std::size_t HasMany<OtherRecord>::Count() const noexcept
194{
195 if (_records)
196 return _records->size();
197
198 if (!_count && _loader.count)
199 const_cast<HasMany<OtherRecord>*>(this)->_count = _loader.count();
200
201 return _count.value_or(0);
202}
203
204template <typename OtherRecord>
205inline LIGHTWEIGHT_FORCE_INLINE bool HasMany<OtherRecord>::IsEmpty() const noexcept
206{
207 return Count() == 0;
208}
209
210template <typename OtherRecord>
211inline LIGHTWEIGHT_FORCE_INLINE OtherRecord const& HasMany<OtherRecord>::At(std::size_t index) const
212{
213 RequireLoaded();
214 return *_records->at(index); // NOLINT(bugprone-unchecked-optional-access)
215}
216
217template <typename OtherRecord>
218inline LIGHTWEIGHT_FORCE_INLINE OtherRecord& HasMany<OtherRecord>::At(std::size_t index)
219{
220 RequireLoaded();
221 return *_records->at(index); // NOLINT(bugprone-unchecked-optional-access)
222}
223
224template <typename OtherRecord>
225inline LIGHTWEIGHT_FORCE_INLINE OtherRecord const& HasMany<OtherRecord>::operator[](std::size_t index) const
226{
227 RequireLoaded();
228 return *(*_records)[index]; // NOLINT(bugprone-unchecked-optional-access)
229}
230
231template <typename OtherRecord>
232inline LIGHTWEIGHT_FORCE_INLINE OtherRecord& HasMany<OtherRecord>::operator[](std::size_t index)
233{
234 RequireLoaded();
235 return *(*_records)[index]; // NOLINT(bugprone-unchecked-optional-access)
236}
237
238template <typename OtherRecord>
239inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::iterator HasMany<OtherRecord>::begin() noexcept
240{
241 RequireLoaded();
242 if (_records)
243 return _records->begin();
244 else
245 return iterator {};
246}
247
248template <typename OtherRecord>
249inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::iterator HasMany<OtherRecord>::end() noexcept
250{
251 RequireLoaded();
252 if (_records)
253 return _records->end();
254 else
255 return iterator {};
256}
257
258template <typename OtherRecord>
259inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::const_iterator HasMany<OtherRecord>::begin() const noexcept
260{
261 RequireLoaded();
262 if (_records)
263 return _records->begin();
264 else
265 return const_iterator {};
266}
267
268template <typename OtherRecord>
269inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::const_iterator HasMany<OtherRecord>::end() const noexcept
270{
271 RequireLoaded();
272 if (_records)
273 return _records->end();
274 else
275 return const_iterator {};
276}
277
278} // namespace Lightweight
This HasMany<OtherRecord> represents a simple one-to-many relationship between two records.
Definition HasMany.hpp:34
OtherRecord value_type
Record type of the "many" side of the relationship.
Definition HasMany.hpp:43
iterator begin() noexcept
Returns an iterator to the beginning of the record list.
Definition HasMany.hpp:239
ReferencedRecordList::iterator iterator
Iterator type for the list of records.
Definition HasMany.hpp:46
void Each(Callable const &callable)
Iterates over the list of records and calls the given callable for each record.
Definition HasMany.hpp:173
std::size_t Count() const noexcept
Retrieves the number of records in this 1-to-many relationship.
Definition HasMany.hpp:193
std::vector< std::shared_ptr< OtherRecord > > ReferencedRecordList
The list of records on the "many" side of the relationship.
Definition HasMany.hpp:40
iterator end() noexcept
Returns an iterator to the end of the record list.
Definition HasMany.hpp:249
bool IsEmpty() const noexcept
Checks if this 1-to-many relationship is empty.
Definition HasMany.hpp:205
ReferencedRecordList & Emplace(ReferencedRecordList &&records) noexcept
Emplaces the given list of records.
Definition HasMany.hpp:157
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:49
OtherRecord ReferencedRecord
The record type of the "many" side of the relationship.
Definition HasMany.hpp:37
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the records.
Definition HasMany.hpp:144
ReferencedRecordList const & All() const noexcept
Retrieves the list of loaded records.
Definition HasMany.hpp:186
OtherRecord const & operator[](std::size_t index) const
Retrieves the record at the given index.
Definition HasMany.hpp:225
OtherRecord const & At(std::size_t index) const
Retrieves the record at the given index.
Definition HasMany.hpp:211