Lightweight 0.20250904.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 = typename ReferencedRecordList::iterator;
47
48 /// Const iterator type for the list of records.
49 using const_iterator = typename 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 [[nodiscard]] iterator begin() noexcept;
102 [[nodiscard]] iterator end() noexcept;
103 [[nodiscard]] const_iterator begin() const noexcept;
104 [[nodiscard]] const_iterator end() const noexcept;
105
106 constexpr std::weak_ordering operator<=>(HasMany const& other) const noexcept = default;
107 constexpr bool operator==(HasMany const& other) const noexcept = default;
108 constexpr bool operator!=(HasMany const& other) const noexcept = default;
109
110 struct Loader
111 {
112 std::function<size_t()> count {};
113 std::function<void()> all {};
114 std::function<void(std::function<void(ReferencedRecord const&)>)> each {};
115
116 std::weak_ordering operator<=>(Loader const& /*other*/) const noexcept
117 {
118 return std::weak_ordering::equivalent; // Loader is not comparable, so we return equivalent
119 }
120 };
121
122 /// Used internally to configure on-demand loading of the records.
123 void SetAutoLoader(Loader loader) noexcept;
124
125 private:
126 void RequireLoaded();
127
128 Loader _loader;
129 std::optional<ReferencedRecordList> _records;
130 std::optional<size_t> _count;
131};
132
133template <typename T>
134constexpr bool IsHasMany = IsSpecializationOf<HasMany, T>;
135
136template <typename OtherRecord>
137inline LIGHTWEIGHT_FORCE_INLINE void HasMany<OtherRecord>::SetAutoLoader(Loader loader) noexcept
138{
139 _loader = std::move(loader);
140}
141
142template <typename OtherRecord>
143inline LIGHTWEIGHT_FORCE_INLINE void HasMany<OtherRecord>::RequireLoaded()
144{
145 if (!_records)
146 _loader.all();
147}
148
149template <typename OtherRecord>
151 ReferencedRecordList&& records) noexcept
152{
153 _records = { std::move(records) };
154 return *_records;
155}
156
157template <typename OtherRecord>
159{
160 RequireLoaded();
161 return *_records; // NOLINT(bugprone-unchecked-optional-access)
162}
163
164template <typename OtherRecord>
165template <typename Callable>
166void HasMany<OtherRecord>::Each(Callable const& callable)
167{
168 if (!_records && _loader.each)
169 {
170 _loader.each(callable);
171 return;
172 }
173
174 for (auto const& record: All())
175 callable(*record);
176}
177
178template <typename OtherRecord>
179inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::ReferencedRecordList const& HasMany<OtherRecord>::All() const noexcept
180{
181 RequireLoaded();
182 return *_records;
183}
184
185template <typename OtherRecord>
186inline LIGHTWEIGHT_FORCE_INLINE std::size_t HasMany<OtherRecord>::Count() const noexcept
187{
188 if (_records)
189 return _records->size();
190
191 if (!_count)
192 const_cast<HasMany<OtherRecord>*>(this)->_count = _loader.count();
193
194 return _count.value_or(0);
195}
196
197template <typename OtherRecord>
198inline LIGHTWEIGHT_FORCE_INLINE bool HasMany<OtherRecord>::IsEmpty() const noexcept
199{
200 return Count() == 0;
201}
202
203template <typename OtherRecord>
204inline LIGHTWEIGHT_FORCE_INLINE OtherRecord const& HasMany<OtherRecord>::At(std::size_t index) const
205{
206 RequireLoaded();
207 return *_records->at(index); // NOLINT(bugprone-unchecked-optional-access)
208}
209
210template <typename OtherRecord>
211inline LIGHTWEIGHT_FORCE_INLINE OtherRecord& HasMany<OtherRecord>::At(std::size_t index)
212{
213 RequireLoaded();
214 return *_records->at(index); // NOLINT(bugprone-unchecked-optional-access)
215}
216
217template <typename OtherRecord>
218inline LIGHTWEIGHT_FORCE_INLINE OtherRecord const& HasMany<OtherRecord>::operator[](std::size_t index) const
219{
220 RequireLoaded();
221 return *(*_records)[index]; // NOLINT(bugprone-unchecked-optional-access)
222}
223
224template <typename OtherRecord>
225inline LIGHTWEIGHT_FORCE_INLINE OtherRecord& HasMany<OtherRecord>::operator[](std::size_t index)
226{
227 RequireLoaded();
228 return *(*_records)[index]; // NOLINT(bugprone-unchecked-optional-access)
229}
230
231template <typename OtherRecord>
232inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::iterator HasMany<OtherRecord>::begin() noexcept
233{
234 RequireLoaded();
235 return _records->begin();
236}
237
238template <typename OtherRecord>
239inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::iterator HasMany<OtherRecord>::end() noexcept
240{
241 RequireLoaded();
242 return _records->end();
243}
244
245template <typename OtherRecord>
246inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::const_iterator HasMany<OtherRecord>::begin() const noexcept
247{
248 RequireLoaded();
249 return _records->begin();
250}
251
252template <typename OtherRecord>
253inline LIGHTWEIGHT_FORCE_INLINE HasMany<OtherRecord>::const_iterator HasMany<OtherRecord>::end() const noexcept
254{
255 RequireLoaded();
256 return _records->end();
257}
258
259} // 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
void Each(Callable const &callable)
Iterates over the list of records and calls the given callable for each record.
Definition HasMany.hpp:166
std::size_t Count() const noexcept
Retrieves the number of records in this 1-to-many relationship.
Definition HasMany.hpp:186
std::vector< std::shared_ptr< OtherRecord > > ReferencedRecordList
The list of records on the "many" side of the relationship.
Definition HasMany.hpp:40
bool IsEmpty() const noexcept
Checks if this 1-to-many relationship is empty.
Definition HasMany.hpp:198
ReferencedRecordList & Emplace(ReferencedRecordList &&records) noexcept
Emplaces the given list of records.
Definition HasMany.hpp:150
OtherRecord ReferencedRecord
The record type of the "many" side of the relationship.
Definition HasMany.hpp:37
typename ReferencedRecordList::const_iterator const_iterator
Const iterator type for the list of records.
Definition HasMany.hpp:49
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the records.
Definition HasMany.hpp:137
ReferencedRecordList const & All() const noexcept
Retrieves the list of loaded records.
Definition HasMany.hpp:179
OtherRecord const & operator[](std::size_t index) const
Retrieves the record at the given index.
Definition HasMany.hpp:218
OtherRecord const & At(std::size_t index) const
Retrieves the record at the given index.
Definition HasMany.hpp:204
typename ReferencedRecordList::iterator iterator
Iterator type for the list of records.
Definition HasMany.hpp:46