Lightweight 0.20250904.0
Loading...
Searching...
No Matches
HasManyThrough.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../Utils.hpp"
6#include "Error.hpp"
7
8#include <reflection-cpp/reflection.hpp>
9
10#include <compare>
11#include <functional>
12#include <memory>
13#include <vector>
14
15namespace Lightweight
16{
17
18/// @brief This API represents a many-to-many relationship between two records through a third record.
19///
20/// @see DataMapper, Field, HasMany
21/// @ingroup DataMapper
22template <typename ReferencedRecordT, typename ThroughRecordT>
24{
25 public:
26 /// The record type of the "through" side of the relationship.
27 using ThroughRecord = ThroughRecordT;
28
29 /// The record type of the "many" side of the relationship.
30 using ReferencedRecord = ReferencedRecordT;
31
32 /// The list of records on the "many" side of the relationship.
33 using ReferencedRecordList = std::vector<std::shared_ptr<ReferencedRecord>>;
34
35 using value_type = ReferencedRecord;
36 using iterator = typename ReferencedRecordList::iterator;
37 using const_iterator = typename ReferencedRecordList::const_iterator;
38
39 /// Retrieves the list of loaded records.
40 [[nodiscard]] ReferencedRecordList const& All() const noexcept;
41
42 /// Retrieves the list of records as mutable reference.
43 [[nodiscard]] ReferencedRecordList& All() noexcept;
44
45 /// Emplaces the given list of records into this relationship.
47
48 /// Retrieves the number of records in this relationship.
49 [[nodiscard]] std::size_t Count() const;
50
51 /// Checks if this relationship is empty.
52 [[nodiscard]] std::size_t IsEmpty() const;
53
54 /// @brief Retrieves the record at the given index.
55 ///
56 /// @param index The index of the record to retrieve.
57 /// @note This method will on-demand load the records if they are not already loaded.
58 /// @note This method will throw if the index is out of bounds.
59 [[nodiscard]] ReferencedRecord const& At(std::size_t index) const;
60
61 /// @brief Retrieves the record at the given index.
62 ///
63 /// @param index The index of the record to retrieve.
64 /// @note This method will on-demand load the records if they are not already loaded.
65 /// @note This method will throw if the index is out of bounds.
66 [[nodiscard]] ReferencedRecord& At(std::size_t index);
67
68 /// @brief Retrieves the record at the given index.
69 ///
70 /// @param index The index of the record to retrieve.
71 /// @note This method will on-demand load the records if they are not already loaded.
72 /// @note This method will NOT throw if the index is out of bounds. The behaviour is undefined.
73 [[nodiscard]] ReferencedRecord const& operator[](std::size_t index) const;
74
75 /// @brief Retrieves the record at the given index.
76 ///
77 /// @param index The index of the record to retrieve.
78 /// @note This method will on-demand load the records if they are not already loaded.
79 /// @note This method will NOT throw if the index is out of bounds. The behaviour is undefined.
80 [[nodiscard]] ReferencedRecord& operator[](std::size_t index);
81
82 [[nodiscard]] iterator begin() noexcept;
83 [[nodiscard]] iterator end() noexcept;
84 [[nodiscard]] const_iterator begin() const noexcept;
85 [[nodiscard]] const_iterator end() const noexcept;
86
87 std::weak_ordering operator<=>(HasManyThrough const& other) const noexcept = default;
88
89 struct Loader
90 {
91 std::function<size_t()> count;
92 std::function<void()> all;
93 std::function<void(std::function<void(ReferencedRecord const&)>)> each;
94 };
95
96 /// Used internally to configure on-demand loading of the records.
97 void SetAutoLoader(Loader loader) noexcept
98 {
99 _loader = std::move(loader);
100 }
101
102 /// Reloads the records from the database.
103 void Reload()
104 {
105 _count = std::nullopt;
106 _records = std::nullopt;
107 RequireLoaded();
108 }
109
110 /// @brief Iterates over all records in this relationship.
111 ///
112 /// @param callable The callable to invoke for each record.
113 /// @note This method will on-demand load the records if they are not already loaded,
114 /// but not hold them all in memory.
115 template <typename Callable>
116 void Each(Callable const& callable)
117 {
118 if (!_records && _loader.each)
119 {
120 _loader.each(callable);
121 return;
122 }
123
124 for (auto const& record: All())
125 callable(*record);
126 }
127
128 private:
129 void RequireLoaded()
130 {
131 if (_records)
132 return;
133
134 if (_loader.all)
135 _loader.all();
136
137 if (!_records)
138 throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>);
139 }
140
141 Loader _loader;
142
143 std::optional<size_t> _count;
144 std::optional<ReferencedRecordList> _records;
145};
146
147template <typename T>
148constexpr bool IsHasManyThrough = IsSpecializationOf<HasManyThrough, T>;
149
150template <typename ReferencedRecordT, typename ThroughRecordT>
152 ThroughRecordT>::All()
153 const noexcept
154{
155 const_cast<HasManyThrough*>(this)->RequireLoaded();
156
157 return _records.value();
158}
159
160template <typename ReferencedRecordT, typename ThroughRecordT>
162 ThroughRecordT>::All() noexcept
163{
164 RequireLoaded();
165
166 return _records.value(); // NOLINT(bugprone-unchecked-optional-access)
167}
168
169template <typename ReferencedRecordT, typename ThroughRecordT>
171 Emplace(ReferencedRecordList&& records) noexcept
172{
173 _records = { std::move(records) };
174 _count = _records->size();
175 return *_records;
176}
177
178template <typename ReferencedRecordT, typename ThroughRecordT>
180{
181 if (_records)
182 return _records->size();
183
184 if (!_count)
185 const_cast<HasManyThrough<ReferencedRecordT, ThroughRecordT>*>(this)->_count = _loader.count();
186
187 return _count.value_or(0);
188}
189
190template <typename ReferencedRecordT, typename ThroughRecordT>
192{
193 return Count() == 0;
194}
195
196template <typename ReferencedRecordT, typename ThroughRecordT>
198 ReferencedRecordT,
199 ThroughRecordT>::At(std::size_t index) const
200{
201 return *All().at(index);
202}
203
204template <typename ReferencedRecordT, typename ThroughRecordT>
210
211template <typename ReferencedRecordT, typename ThroughRecordT>
213 ReferencedRecordT,
214 ThroughRecordT>::operator[](std::size_t index) const
215{
216 return *All()[index];
217}
218
219template <typename ReferencedRecordT, typename ThroughRecordT>
225
226template <typename ReferencedRecordT, typename ThroughRecordT>
227HasManyThrough<ReferencedRecordT, ThroughRecordT>::iterator HasManyThrough<ReferencedRecordT,
228 ThroughRecordT>::begin() noexcept
229{
230 return All().begin();
231}
232
233template <typename ReferencedRecordT, typename ThroughRecordT>
234HasManyThrough<ReferencedRecordT, ThroughRecordT>::iterator HasManyThrough<ReferencedRecordT, ThroughRecordT>::end() noexcept
235{
236 return All().end();
237}
238
239template <typename ReferencedRecordT, typename ThroughRecordT>
240HasManyThrough<ReferencedRecordT, ThroughRecordT>::const_iterator HasManyThrough<ReferencedRecordT, ThroughRecordT>::begin()
241 const noexcept
242{
243 return All().begin();
244}
245
246template <typename ReferencedRecordT, typename ThroughRecordT>
247HasManyThrough<ReferencedRecordT, ThroughRecordT>::const_iterator HasManyThrough<ReferencedRecordT, ThroughRecordT>::end()
248 const noexcept
249{
250 return All().end();
251}
252
253} // namespace Lightweight
This API represents a many-to-many relationship between two records through a third record.
void Each(Callable const &callable)
Iterates over all records in this relationship.
std::size_t Count() const
Retrieves the number of records in this relationship.
ReferencedRecord const & operator[](std::size_t index) const
Retrieves the record at the given index.
std::vector< std::shared_ptr< ReferencedRecord > > ReferencedRecordList
The list of records on the "many" side of the relationship.
ReferencedRecordList const & All() const noexcept
Retrieves the list of loaded records.
void Reload()
Reloads the records from the database.
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the records.
ReferencedRecord const & At(std::size_t index) const
Retrieves the record at the given index.
ThroughRecordT ThroughRecord
The record type of the "through" side of the relationship.
std::size_t IsEmpty() const
Checks if this relationship is empty.
ReferencedRecordList & Emplace(ReferencedRecordList &&records) noexcept
Emplaces the given list of records into this relationship.
ReferencedRecordT ReferencedRecord
The record type of the "many" side of the relationship.
Represents an error when a record is required to be loaded but is not.
Definition Error.hpp:16