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