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