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