Lightweight 0.20250627.0
Loading...
Searching...
No Matches
BelongsTo.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 "../Utils.hpp"
9#include "Error.hpp"
10#include "Field.hpp"
11
12#include <compare>
13#include <optional>
14#include <string_view>
15#include <type_traits>
16
17/// @brief Represents a one-to-one relationship.
18///
19/// The `TheReferencedField` parameter is the field in the other record that references the current record,
20/// in the form of `&OtherRecord::Field`.
21/// Other Field must be a primary key.
22///
23/// @tparam TheReferencedField The field in the other record that references the current record.
24/// @tparam ColumnNameOverrideString If not an empty string, this value will be used as the column name in the database.
25///
26/// @ingroup DataMapper
27///
28/// @code
29/// struct User {
30/// Field<SqlGuid, PrimaryKey::AutoAssign> id;
31/// Field<SqlAnsiString<30>> name;
32/// };
33/// struct Email {
34/// Field<SqlGuid, PrimaryKey::AutoAssign> id;
35/// Field<SqlAnsiString<40>> address;
36/// BelongsTo<&User::id> user;
37/// BelongsTo<&User::id, SqlRealName<"the_user_id">> user; // also possible to customize the column name
38/// };
39/// @endcode
40template <auto TheReferencedField, auto ColumnNameOverrideString = std::nullopt>
42{
43 public:
44 /// The field in the other record that references the current record.
45 static constexpr auto ReferencedField = TheReferencedField;
46
47 /// If not an empty string, this value will be used as the column name in the database.
48 static constexpr std::string_view ColumnNameOverride = []() consteval {
49 if constexpr (!std::same_as<decltype(ColumnNameOverrideString), std::nullopt_t>)
50 return std::string_view { ColumnNameOverrideString };
51 else
52 return std::string_view {};
53 }();
54
55 /// Represents the record type of the other field.
56 using ReferencedRecord = MemberClassType<decltype(TheReferencedField)>;
57
58 static_assert(std::remove_cvref_t<decltype(std::declval<ReferencedRecord>().*ReferencedField)>::IsPrimaryKey,
59 "The referenced field must be a primary key.");
60
61 /// Represents the column type of the foreign key, matching the primary key of the other record.
62 using ValueType = typename std::remove_cvref_t<decltype(std::declval<ReferencedRecord>().*ReferencedField)>::ValueType;
63
64 static constexpr auto IsOptional = true;
65 static constexpr auto IsMandatory = !IsOptional;
66 static constexpr auto IsPrimaryKey = false;
67 static constexpr auto IsAutoIncrementPrimaryKey = false;
68
69 template <typename... S>
70 requires std::constructible_from<ValueType, S...>
71 constexpr BelongsTo(S&&... value) noexcept:
72 _referencedFieldValue(std::forward<S>(value)...)
73 {
74 }
75
76 constexpr BelongsTo(ReferencedRecord const& other) noexcept:
77 _referencedFieldValue { (other.*ReferencedField).Value() },
78 _loaded { true },
79 _record { std::make_unique<ReferencedRecord>(other) }
80 {
81 }
82
83 constexpr BelongsTo(BelongsTo const& other) noexcept:
84 _referencedFieldValue(other._referencedFieldValue),
85 _loader(std::move(other._loader)),
86 _loaded(other._loaded),
87 _modified(other._modified),
88 _record(other._record ? std::make_unique<ReferencedRecord>(*other._record) : nullptr)
89 {
90 }
91
92 constexpr BelongsTo(BelongsTo&& other) noexcept:
93 _referencedFieldValue(std::move(other._referencedFieldValue)),
94 _loader(std::move(other._loader)),
95 _loaded(other._loaded),
96 _modified(other._modified),
97 _record(std::move(other._record))
98 {
99 }
100
101 BelongsTo& operator=(SqlNullType /*nullValue*/) noexcept
102 {
103 if (!_referencedFieldValue)
104 return *this;
105 _loaded = false;
106 _record = std::nullopt;
107 _referencedFieldValue = {};
108 _modified = true;
109 return *this;
110 }
111
112 BelongsTo& operator=(ReferencedRecord& other)
113 {
114 if (_referencedFieldValue == (other.*ReferencedField).Value())
115 return *this;
116 _loaded = true;
117 _record = std::make_unique<ReferencedRecord>(other);
118 _referencedFieldValue = (other.*ReferencedField).Value();
119 _modified = true;
120 return *this;
121 }
122
123 BelongsTo& operator=(BelongsTo const& other)
124 {
125 if (this == &other)
126 return *this;
127
128 _referencedFieldValue = other._referencedFieldValue;
129 _loader = std::move(other._loader);
130 _loaded = other._loaded;
131 _modified = other._modified;
132 _record = other._record ? std::make_unique<ReferencedRecord>(*other._record) : nullptr;
133
134 return *this;
135 }
136
137 BelongsTo& operator=(BelongsTo&& other) noexcept
138 {
139 if (this == &other)
140 return *this;
141 _referencedFieldValue = std::move(other._referencedFieldValue);
142 _loader = std::move(other._loader);
143 _loaded = other._loaded;
144 _modified = other._modified;
145 _record = std::move(other._record);
146 other._loaded = false;
147 return *this;
148 }
149
150 ~BelongsTo() noexcept = default;
151
152 // clang-format off
153
154 /// Marks the field as modified or unmodified.
155 LIGHTWEIGHT_FORCE_INLINE constexpr void SetModified(bool value) noexcept { _modified = value; }
156
157 /// Checks if the field is modified.
158 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept { return _modified; }
159
160 /// Retrieves the reference to the value of the field.
161 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const& Value() const noexcept { return _referencedFieldValue; }
162
163 /// Retrieves the mutable reference to the value of the field.
164 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType& MutableValue() noexcept { return _referencedFieldValue; }
165
166 /// Retrieves a record from the relationship.
167 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& Record() { RequireLoaded(); return *_record; }
168
169 /// Retrieves an immutable reference to the record from the relationship.
170 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& Record() const { RequireLoaded(); return *_record; }
171
172 /// Checks if the record is loaded into memory.
173 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsLoaded() const noexcept { return _loaded; }
174
175 /// Unloads the record from memory.
176 LIGHTWEIGHT_FORCE_INLINE void Unload() noexcept { _record = nullptr; _loaded = false; }
177
178 /// Retrieves the record from the relationship.
179 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& operator*() noexcept { RequireLoaded(); return *_record; }
180
181 /// Retrieves the record from the relationship.
182 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& operator*() const noexcept { RequireLoaded(); return *_record; }
183
184 /// Retrieves the record from the relationship.
185 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* operator->() { RequireLoaded(); return _record.get(); }
186
187 /// Retrieves the record from the relationship.
188 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const* operator->() const { RequireLoaded(); return _record.get(); }
189
190 /// Checks if the field value is NULL.
191 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept { return !_referencedFieldValue; }
192
193 /// Checks if the field value is not NULL.
194 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr explicit operator bool() const noexcept { return static_cast<bool>(_referencedFieldValue); }
195
196 // clang-format on
197
198 /// Emplaces a record into the relationship. This will mark the relationship as loaded.
199 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& EmplaceRecord()
200 {
201 _loaded = true;
202 _record = std::make_unique<ReferencedRecord>();
203 return *_record;
204 }
205
206 LIGHTWEIGHT_FORCE_INLINE void BindOutputColumn(SQLSMALLINT outputIndex, SqlStatement& stmt)
207 {
208 stmt.BindOutputColumn(outputIndex, &_referencedFieldValue);
209 }
210
211 template <auto OtherReferencedField>
212 std::weak_ordering operator<=>(BelongsTo<OtherReferencedField> const& other) const noexcept
213 {
214 return _referencedFieldValue <=> other.Value();
215 }
216
217 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
218 std::weak_ordering operator<=>(Field<T, IsPrimaryKeyValue> const& other) const noexcept
219 {
220 return _referencedFieldValue <=> other.Value();
221 }
222
223 template <auto OtherReferencedField>
224 bool operator==(BelongsTo<OtherReferencedField> const& other) const noexcept
225 {
226 return (_referencedFieldValue <=> other.Value()) == std::weak_ordering::equivalent;
227 }
228
229 template <auto OtherReferencedField>
230 bool operator!=(BelongsTo<OtherReferencedField> const& other) const noexcept
231 {
232 return (_referencedFieldValue <=> other.Value()) != std::weak_ordering::equivalent;
233 }
234
235 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
236 bool operator==(Field<T, IsPrimaryKeyValue> const& other) const noexcept
237 {
238 return (_referencedFieldValue <=> other.Value()) == std::weak_ordering::equivalent;
239 }
240
241 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
242 bool operator!=(Field<T, IsPrimaryKeyValue> const& other) const noexcept
243 {
244 return (_referencedFieldValue <=> other.Value()) != std::weak_ordering::equivalent;
245 }
246
247 struct Loader
248 {
249 std::function<std::optional<ReferencedRecord>()> loadReference {};
250 };
251
252 /// Used internally to configure on-demand loading of the record.
253 void SetAutoLoader(Loader loader) noexcept
254 {
255 _loader = std::move(loader);
256 }
257
258 private:
259 void RequireLoaded()
260 {
261 if (_loaded)
262 return;
263
264 if (_loader.loadReference)
265 {
266 auto value = _loader.loadReference();
267 if (value)
268 {
269 _record = std::make_unique<ReferencedRecord>(std::move(value.value()));
270 _loaded = true;
271 }
272 }
273
274 if (!_loaded)
275 throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>);
276 }
277
278 ValueType _referencedFieldValue {};
279 Loader _loader {};
280 bool _loaded = false;
281 bool _modified = false;
282 std::unique_ptr<ReferencedRecord> _record {};
283};
284
285template <auto ReferencedField>
286std::ostream& operator<<(std::ostream& os, BelongsTo<ReferencedField> const& belongsTo)
287{
288 return os << belongsTo.Value();
289}
290
291namespace detail
292{
293template <typename T>
294struct IsBelongsToType: std::false_type
295{
296};
297
298template <auto ReferencedField, auto ColumnNameOverrideString>
299struct IsBelongsToType<BelongsTo<ReferencedField, ColumnNameOverrideString>>: std::true_type
300{
301};
302
303} // namespace detail
304
305template <typename T>
306constexpr bool IsBelongsTo = detail::IsBelongsToType<std::remove_cvref_t<T>>::value;
307
308template <auto ReferencedField, auto ColumnNameOverrideString>
309struct SqlDataBinder<BelongsTo<ReferencedField, ColumnNameOverrideString>>
310{
312 using InnerType = typename SelfType::ValueType;
313
314 static constexpr auto ColumnType = SqlDataBinder<InnerType>::ColumnType;
315
316 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
317 SQLUSMALLINT column,
318 SelfType const& value,
320 {
321 return SqlDataBinder<InnerType>::InputParameter(stmt, column, value.Value(), cb);
322 }
323
324 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
325 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, SelfType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
326 {
327 auto const sqlReturn = SqlDataBinder<InnerType>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
328 cb.PlanPostProcessOutputColumn([result]() { result->SetModified(true); });
329 return sqlReturn;
330 }
331
332 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(
333 SQLHSTMT stmt, SQLUSMALLINT column, SelfType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept
334 {
335 auto const sqlReturn = SqlDataBinder<InnerType>::GetColumn(stmt, column, &result->MutableValue(), indicator, cb);
336 if (SQL_SUCCEEDED(sqlReturn))
337 result->SetModified(true);
338 return sqlReturn;
339 }
340};
Represents a one-to-one relationship.
Definition BelongsTo.hpp:42
static constexpr std::string_view ColumnNameOverride
If not an empty string, this value will be used as the column name in the database.
Definition BelongsTo.hpp:48
LIGHTWEIGHT_FORCE_INLINE constexpr ValueType & MutableValue() noexcept
Retrieves the mutable reference to the value of the field.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & operator*() noexcept
Retrieves the record from the relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const & Record() const
Retrieves an immutable reference to the record from the relationship.
MemberClassType< decltype(TheReferencedField)> ReferencedRecord
Represents the record type of the other field.
Definition BelongsTo.hpp:56
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the record.
static constexpr auto ReferencedField
The field in the other record that references the current record.
Definition BelongsTo.hpp:45
LIGHTWEIGHT_FORCE_INLINE constexpr void SetModified(bool value) noexcept
Marks the field as modified or unmodified.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const * operator->() const
Retrieves the record from the relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord * operator->()
Retrieves the record from the relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & EmplaceRecord()
Emplaces a record into the relationship. This will mark the relationship as loaded.
LIGHTWEIGHT_FORCE_INLINE void Unload() noexcept
Unloads the record from memory.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & Record()
Retrieves a record from the relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const & Value() const noexcept
Retrieves the reference to the value of the field.
typename std::remove_cvref_t< decltype(std::declval< ReferencedRecord >().*ReferencedField)>::ValueType ValueType
Represents the column type of the foreign key, matching the primary key of the other record.
Definition BelongsTo.hpp:62
LIGHTWEIGHT_FORCE_INLINE constexpr bool IsLoaded() const noexcept
Checks if the record is loaded into memory.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const & operator*() const noexcept
Retrieves the record from the relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept
Checks if the field value is NULL.
LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept
Checks if the field is modified.
Represents an error when a record is required to be loaded but is not.
Definition Error.hpp:13
High level API for (prepared) raw SQL statements.
Represents a single column in a table.
Definition Field.hpp:79