Lightweight 0.20260303.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]] std::size_t 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.
ThroughRecordT ThroughRecord
The record type of the "through" side of the relationship.
std::size_t IsEmpty() const
Checks if this relationship is empty.
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