Lightweight 0.20260617.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 /// Value type for range-based iteration.
37 /// Iterator type for the list of records.
38 using iterator = ReferencedRecordList::iterator;
39 /// Const iterator type for the list of records.
40 using const_iterator = ReferencedRecordList::const_iterator;
41
42 /// Retrieves the list of loaded records.
43 [[nodiscard]] ReferencedRecordList const& All() const noexcept;
44
45 /// Retrieves the list of records as mutable reference.
46 [[nodiscard]] ReferencedRecordList& All() noexcept;
47
48 /// Emplaces the given list of records into this relationship.
50
51 /// Retrieves the number of records in this relationship.
52 [[nodiscard]] std::size_t Count() const;
53
54 /// Checks if this relationship is empty.
55 [[nodiscard]] bool IsEmpty() const;
56
57 /// @brief Retrieves the record at the given index.
58 ///
59 /// @param index The index of the record to retrieve.
60 /// @note This method will on-demand load the records if they are not already loaded.
61 /// @note This method will throw if the index is out of bounds.
62 [[nodiscard]] ReferencedRecord const& At(std::size_t index) const;
63
64 /// @brief Retrieves the record at the given index.
65 ///
66 /// @param index The index of the record to retrieve.
67 /// @note This method will on-demand load the records if they are not already loaded.
68 /// @note This method will throw if the index is out of bounds.
69 [[nodiscard]] ReferencedRecord& At(std::size_t index);
70
71 /// @brief Retrieves the record at the given index.
72 ///
73 /// @param index The index of the record to retrieve.
74 /// @note This method will on-demand load the records if they are not already loaded.
75 /// @note This method will NOT throw if the index is out of bounds. The behaviour is undefined.
76 [[nodiscard]] ReferencedRecord const& operator[](std::size_t index) const;
77
78 /// @brief Retrieves the record at the given index.
79 ///
80 /// @param index The index of the record to retrieve.
81 /// @note This method will on-demand load the records if they are not already loaded.
82 /// @note This method will NOT throw if the index is out of bounds. The behaviour is undefined.
83 [[nodiscard]] ReferencedRecord& operator[](std::size_t index);
84
85 /// Returns an iterator to the beginning of the record list.
86 [[nodiscard]] iterator begin() noexcept;
87 /// Returns an iterator to the end of the record list.
88 [[nodiscard]] iterator end() noexcept;
89 /// Returns a const iterator to the beginning of the record list.
90 [[nodiscard]] const_iterator begin() const noexcept;
91 /// Returns a const iterator to the end of the record list.
92 [[nodiscard]] const_iterator end() const noexcept;
93
94 /// Default three-way comparison operator.
95 std::weak_ordering operator<=>(HasManyThrough const& other) const noexcept = default;
96
97 struct Loader
98 {
99 std::function<size_t()> count;
100 std::function<ReferencedRecordList()> all;
101 std::function<void(std::function<void(ReferencedRecord const&)>)> each;
102 };
103
104 /// Used internally to configure on-demand loading of the records.
105 void SetAutoLoader(Loader loader) noexcept
106 {
107 _loader = std::move(loader);
108 }
109
110 /// Reloads the records from the database.
111 void Reload()
112 {
113 _count = std::nullopt;
114 _records = std::nullopt;
115 RequireLoaded();
116 }
117
118 /// @brief Iterates over all records in this relationship.
119 ///
120 /// @param callable The callable to invoke for each record.
121 /// @note This method will on-demand load the records if they are not already loaded,
122 /// but not hold them all in memory.
123 template <typename Callable>
124 void Each(Callable const& callable)
125 {
126 if (!_records && _loader.each)
127 {
128 _loader.each(callable);
129 return;
130 }
131
132 for (auto const& record: All())
133 callable(*record);
134 }
135
136 private:
137 void RequireLoaded()
138 {
139 if (_records)
140 return;
141
142 if (_loader.all)
143 _records = _loader.all();
144
145 if (!_records)
146 throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>);
147 }
148
149 Loader _loader;
150
151 std::optional<size_t> _count;
152 std::optional<ReferencedRecordList> _records;
153};
154
155template <typename T>
156constexpr bool IsHasManyThrough = IsSpecializationOf<HasManyThrough, T>;
157
158template <typename ReferencedRecordT, typename ThroughRecordT>
160 ThroughRecordT>::All()
161 const noexcept
162{
163 const_cast<HasManyThrough*>(this)->RequireLoaded();
164
165 return _records.value();
166}
167
168template <typename ReferencedRecordT, typename ThroughRecordT>
170 ThroughRecordT>::All() noexcept
171{
172 RequireLoaded();
173
174 return _records.value(); // NOLINT(bugprone-unchecked-optional-access)
175}
176
177template <typename ReferencedRecordT, typename ThroughRecordT>
179 Emplace(ReferencedRecordList&& records) noexcept
180{
181 _records = { std::move(records) };
182 _count = _records->size();
183 return *_records;
184}
185
186template <typename ReferencedRecordT, typename ThroughRecordT>
188{
189 if (_records)
190 return _records->size();
191
192 if (!_count)
193 const_cast<HasManyThrough<ReferencedRecordT, ThroughRecordT>*>(this)->_count = _loader.count();
194
195 return _count.value_or(0);
196}
197
198template <typename ReferencedRecordT, typename ThroughRecordT>
200{
201 return Count() == 0;
202}
203
204template <typename ReferencedRecordT, typename ThroughRecordT>
206 ReferencedRecordT,
207 ThroughRecordT>::At(std::size_t index) const
208{
209 return *All().at(index);
210}
211
212template <typename ReferencedRecordT, typename ThroughRecordT>
218
219template <typename ReferencedRecordT, typename ThroughRecordT>
221 ReferencedRecordT,
222 ThroughRecordT>::operator[](std::size_t index) const
223{
224 return *All()[index];
225}
226
227template <typename ReferencedRecordT, typename ThroughRecordT>
233
234template <typename ReferencedRecordT, typename ThroughRecordT>
236 ThroughRecordT>::begin() noexcept
237{
238 return All().begin();
239}
240
241template <typename ReferencedRecordT, typename ThroughRecordT>
246
247template <typename ReferencedRecordT, typename ThroughRecordT>
253
254template <typename ReferencedRecordT, typename ThroughRecordT>
260
261} // 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.
ReferencedRecordList::iterator iterator
Iterator type for the list of records.
ReferencedRecord const & operator[](std::size_t index) const
Retrieves the record at the given index.
iterator begin() noexcept
Returns an iterator to the beginning of the record list.
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.
bool IsEmpty() const
Checks if this relationship is empty.
ThroughRecordT ThroughRecord
The record type of the "through" side of the relationship.
iterator end() noexcept
Returns an iterator to the end of the record list.
ReferencedRecordList::const_iterator const_iterator
Const iterator type for the list of records.
ReferencedRecordList & Emplace(ReferencedRecordList &&records) noexcept
Emplaces the given list of records into this relationship.
ReferencedRecord value_type
Value type for range-based iteration.
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