Lightweight 0.20261105.1
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 "../SqlColumnTypeDefinitions.hpp"
8#include "../Utils.hpp"
9#include "Error.hpp"
10#include "Field.hpp"
11
12namespace Lightweight
13{
14class SqlStatement;
15}
16
17#include <compare>
18#include <functional>
19#include <memory>
20#include <optional>
21#include <string_view>
22#include <type_traits>
23
24namespace Lightweight
25{
26
27/// @brief Helper function to use with std::optional<std::reference_wrapper<T>>
28/// like this .transform(Unwrap).value_or({})
29auto inline Unwrap = [](auto v) {
30 return v.get();
31};
32
33/// @brief Represents the many-to-one side of a foreign-key relationship.
34///
35/// This is the record that *owns* the foreign-key column: many records holding a `BelongsTo` may
36/// reference the same target record. For a one-to-one relation through a join table, see
37/// `HasOneThrough`; for the inverse (one-to-many) side, see `HasMany`.
38///
39/// The `TheReferencedField` parameter is the field in the other record that references the current record,
40/// in the form of `&OtherRecord::Field`.
41/// Other Field must be a primary key.
42///
43/// @tparam TheReferencedField The field in the other record that references the current record.
44/// @tparam ColumnNameOverrideString If not an empty string, this value will be used as the column name in the database.
45///
46/// @ingroup DataMapper
47///
48/// @code
49/// struct User {
50/// Field<SqlGuid, PrimaryKey::AutoAssign> id;
51/// Field<SqlAnsiString<30>> name;
52/// };
53/// struct Email {
54/// Field<SqlGuid, PrimaryKey::AutoAssign> id;
55/// Field<SqlAnsiString<40>> address;
56/// BelongsTo<&User::id> user;
57/// // Also possible to customize the column name
58/// BelongsTo<&User::id, SqlRealName<"the_user_id">, SqlNullable::Null> maybe_user;
59/// };
60/// @endcode
61template <auto TheReferencedField, auto ColumnNameOverrideString = std::nullopt, SqlNullable Nullable = SqlNullable::NotNull>
63{
64 public:
65 /// The field in the other record that references the current record.
66 static constexpr auto ReferencedField = TheReferencedField;
67
68 /// If not an empty string, this value will be used as the column name in the database.
69 static constexpr std::string_view ColumnNameOverride = []() consteval {
70 if constexpr (!std::same_as<decltype(ColumnNameOverrideString), std::nullopt_t>)
71 return std::string_view { ColumnNameOverrideString };
72 else
73 return std::string_view {};
74 }();
75
76#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
77 /// Represents the record type of the other field.
78 using ReferencedRecord = MemberClassType<TheReferencedField>;
79
80 /// Represents the base column type of the foreign key, matching the primary key of the other record.
81 using BaseType = typename[:std::meta::type_of(ReferencedField):] ::ValueType;
82
83 static_assert(std::remove_cvref_t<decltype(std::declval<ReferencedRecord>().[:ReferencedField:])>::IsPrimaryKey,
84 "The referenced field must be a primary key.");
85#else
86 /// Represents the record type of the other field.
87 using ReferencedRecord = MemberClassType<decltype(TheReferencedField)>;
88
89 static_assert(std::remove_cvref_t<decltype(std::declval<ReferencedRecord>().*ReferencedField)>::IsPrimaryKey,
90 "The referenced field must be a primary key.");
91
92 /// Represents the base column type of the foreign key, matching the primary key of the other record.
93 using BaseType = std::remove_cvref_t<decltype(std::declval<ReferencedRecord>().*ReferencedField)>::ValueType;
94#endif
95
96 /// Represents the value type of the foreign key,
97 /// which can be either an optional or a non-optional type of the referenced field,
98 using ValueType = std::conditional_t<Nullable == SqlNullable::Null, std::optional<BaseType>, BaseType>;
99
100 /// Indicates whether this relationship is optional (nullable).
101 static constexpr auto IsOptional = Nullable == SqlNullable::Null;
102 /// Indicates whether this relationship is mandatory (non-nullable).
103 static constexpr auto IsMandatory = !IsOptional;
104 /// Indicates that a BelongsTo field is never a primary key.
105 static constexpr auto IsPrimaryKey = false;
106 /// Indicates that a BelongsTo field is never an auto-increment primary key.
107 static constexpr auto IsAutoIncrementPrimaryKey = false;
108
109 /// Constructs a new BelongsTo with the given value(s) forwarded to the underlying value type.
110 template <typename... S>
111 requires std::constructible_from<ValueType, S...>
112 constexpr BelongsTo(S&&... value) noexcept:
113 _referencedFieldValue(std::forward<S>(value)...)
114 {
115 }
116
117 /// Constructs a new BelongsTo from the given referenced record, copying its primary key.
118 constexpr BelongsTo(ReferencedRecord const& other) noexcept:
119#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
120 _referencedFieldValue { (other.[:ReferencedField:]).Value() },
121#else
122 _referencedFieldValue { (other.*ReferencedField).Value() },
123#endif
124 _loaded { true },
125 _record { std::make_unique<ReferencedRecord>(other) }
126 {
127 }
128
129 /// Copy constructor.
130 constexpr BelongsTo(BelongsTo const& other) noexcept:
131 _referencedFieldValue(other._referencedFieldValue),
132 _loader(std::move(other._loader)),
133 _loaded(other._loaded),
134 _modified(other._modified),
135 _record(other._record ? std::make_unique<ReferencedRecord>(*other._record) : nullptr),
136 _loadError(other._loadError)
137 {
138 }
139
140 /// Move constructor.
141 constexpr BelongsTo(BelongsTo&& other) noexcept:
142 _referencedFieldValue(std::move(other._referencedFieldValue)),
143 _loader(std::move(other._loader)),
144 _loaded(other._loaded),
145 _modified(other._modified),
146 _record(std::move(other._record)),
147 _loadError(other._loadError)
148 {
149 }
150
151 /// Assigns NULL to the relationship, clearing the loaded record.
152 BelongsTo& operator=(SqlNullType /*nullValue*/) noexcept
153 {
154 if (!_referencedFieldValue)
155 return *this;
156 _loaded = false;
157 _record.reset();
158 _loadError.reset();
159 _referencedFieldValue = {};
160 _modified = true;
161 return *this;
162 }
163
164 /// Assigns a bare foreign-key value, updating the foreign key and marking the field modified.
165 ///
166 /// This is the sibling of Field<T>::operator=(S&&). Without it, `record.fk = someKeyValue`
167 /// would bind to the converting constructor plus move-assignment, and the temporary's
168 /// default-constructed `_modified` (false) would be copied over this field — leaving the
169 /// change invisible to DataMapper::Update(), which gates its SET clause on IsModified().
170 ///
171 /// Any previously loaded record is dropped, since it no longer corresponds to the new key.
172 ///
173 /// @param value The referenced record's primary-key value to point at.
174 /// @return Reference to this field.
175 template <typename S>
176 requires(std::constructible_from<ValueType, S> && !std::same_as<std::remove_cvref_t<S>, BelongsTo>
177 && !std::same_as<std::remove_cvref_t<S>, ReferencedRecord>
178 && !std::same_as<std::remove_cvref_t<S>, SqlNullType>)
179 constexpr BelongsTo& operator=(S&& value) noexcept
180 {
181 _referencedFieldValue = ValueType { std::forward<S>(value) };
182 _loaded = false;
183 _record.reset();
184 _loadError.reset();
185 _modified = true;
186 return *this;
187 }
188
189 /// Assigns a referenced record, updating the foreign key and loaded state.
191 {
192#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
193 if (_referencedFieldValue == (other.[:ReferencedField:]).Value())
194#else
195 if (_referencedFieldValue == (other.*ReferencedField).Value())
196#endif
197 return *this;
198 _loaded = true;
199 _record = std::make_unique<ReferencedRecord>(other);
200 _loadError.reset();
201#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
202 _referencedFieldValue = (other.[:ReferencedField:]).Value();
203#else
204 _referencedFieldValue = (other.*ReferencedField).Value();
205#endif
206 _modified = true;
207 return *this;
208 }
209
210 /// Copy assignment operator.
212 {
213 if (this == &other)
214 return *this;
215
216 _referencedFieldValue = other._referencedFieldValue;
217 _loader = std::move(other._loader);
218 _loaded = other._loaded;
219 _modified = other._modified;
220 _record = other._record ? std::make_unique<ReferencedRecord>(*other._record) : nullptr;
221 _loadError = other._loadError;
222
223 return *this;
224 }
225
226 /// Move assignment operator.
227 BelongsTo& operator=(BelongsTo&& other) noexcept
228 {
229 if (this == &other)
230 return *this;
231 _referencedFieldValue = std::move(other._referencedFieldValue);
232 _loader = std::move(other._loader);
233 _loaded = other._loaded;
234 _modified = other._modified;
235 _record = std::move(other._record);
236 _loadError = other._loadError;
237 other._loaded = false;
238 return *this;
239 }
240
241 ~BelongsTo() noexcept = default;
242
243 /// Marks the field as modified or unmodified.
244 LIGHTWEIGHT_FORCE_INLINE constexpr void SetModified(bool value) noexcept
245 {
246 _modified = value;
247 }
248
249 /// Checks if the field is modified.
250 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept
251 {
252 return _modified;
253 }
254
255 /// Retrieves the reference to the value of the field.
256 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const& Value() const noexcept
257 {
258 return _referencedFieldValue;
259 }
260
261 /// Retrieves the mutable reference to the value of the field.
262 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType& MutableValue() noexcept
263 {
264 return _referencedFieldValue;
265 }
266
267 // NOLINTBEGIN(cppcoreguidelines-missing-std-forward)
268
269 /// @brief Retrieves the referenced record, loading it on first access.
270 ///
271 /// Never throws for an unavailable record: the reason comes back as the error instead.
272 ///
273 /// @return The record; or @ref RelationError::NotFound when the foreign key is NULL or points at no
274 /// row, @ref RelationError::NotConfigured when there is no loader (a hand-built record, or
275 /// one read with `loadRelations = false`), @ref RelationError::Outdated when the default
276 /// connection string changed since the record was read, @ref RelationError::QueryFailed when
277 /// the load query failed.
278 template <typename Self>
279 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr auto Record(this Self&& self) -> RelationResult<std::reference_wrapper<
280 std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, ReferencedRecord const, ReferencedRecord>>>
281 {
282 using Target =
283 std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, ReferencedRecord const, ReferencedRecord>;
284 return self.Load().transform([&self] { return std::reference_wrapper<Target> { *self._record }; });
285 }
286
287 /// @brief Retrieves the referenced record. Only available when the relationship is mandatory.
288 /// @throws SqlRequireLoadedError The record is unavailable; see @ref Record() for the reasons, which
289 /// reports them without throwing.
290 template <typename Self>
291 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& operator*(this Self&& self)
292 requires(IsMandatory)
293 {
294 self.LoadOrThrow();
295 return *self._record;
296 }
297
298 /// @brief Retrieves the referenced record. Only available when the relationship is mandatory.
299 /// @throws SqlRequireLoadedError The record is unavailable; see @ref Record() for the reasons, which
300 /// reports them without throwing.
301 template <typename Self>
302 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* operator->(this Self&& self)
303 requires(IsMandatory)
304 {
305 self.LoadOrThrow();
306 return self._record.get();
307 }
308
309 // NOLINTEND(cppcoreguidelines-missing-std-forward)
310
311 /// Checks if the field value is NULL.
312 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept
313 {
314 return !_referencedFieldValue;
315 }
316
317 /// Checks if the field value is not NULL.
318 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr explicit operator bool() const noexcept
319 {
320 return static_cast<bool>(_referencedFieldValue);
321 }
322
323 /// @brief Returns the already-loaded referenced record, or `nullptr` when none is loaded.
324 ///
325 /// Unlike `Record()`, this never runs the on-demand loader: it reports what is present right
326 /// now. That is what lets the batched relation loading walk one level deeper (`With<A, B>()`)
327 /// without turning the walk itself into the N+1 it exists to remove.
328 ///
329 /// @return Pointer to the loaded record, or `nullptr` if the relation is unloaded or NULL.
330 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* LoadedRecord() noexcept
331 {
332 return _record.get();
333 }
334
335 /// @copydoc LoadedRecord()
336 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const* LoadedRecord() const noexcept
337 {
338 return _record.get();
339 }
340
341 /// Emplaces a record into the relationship. This will mark the relationship as loaded.
342 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& EmplaceRecord()
343 {
344 _loaded = true;
345 _loadError.reset();
346 _record = std::make_unique<ReferencedRecord>();
347 return *_record;
348 }
349
350 /// Adopts an eagerly fetched referenced record, marking the relationship loaded.
351 ///
352 /// This is the sink for a record that was already read from the database, as opposed to
353 /// `operator=(ReferencedRecord&)`, which *establishes* the relationship and therefore rewrites
354 /// the foreign key and sets the modified flag. Here the foreign key is already whatever the row
355 /// said it was, so it is deliberately left untouched and the field stays unmodified - adopting a
356 /// fetched value is not a pending change to be written back.
357 ///
358 /// An empty @p record leaves the relationship unloaded rather than clearing the foreign key: a
359 /// mandatory relationship whose target row is missing is a data-integrity problem to be
360 /// surfaced by the accessor, not silently turned into NULL.
361 ///
362 /// @param record The fetched record, or `std::nullopt` if the referenced row was absent.
363 LIGHTWEIGHT_FORCE_INLINE void AdoptFetchedRecord(std::optional<ReferencedRecord> record)
364 {
365 if (!record.has_value())
366 return;
367
368 _record = std::make_unique<ReferencedRecord>(std::move(record).value());
369 _loaded = true;
370 _loadError.reset();
371 }
372
373 /// Binds the foreign key value to the given output column index on the statement.
374 template <typename Stmt>
375 LIGHTWEIGHT_FORCE_INLINE void BindOutputColumn(SQLSMALLINT outputIndex, Stmt& stmt)
376 {
377 stmt.BindOutputColumn(outputIndex, &_referencedFieldValue);
378 }
379
380 /// Three-way comparison operator.
381 std::weak_ordering operator<=>(BelongsTo const& other) const noexcept
382 {
383 return _referencedFieldValue <=> other.Value();
384 }
385
386 /// Three-way comparison operator with a Field.
387 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
388 std::weak_ordering operator<=>(Field<T, IsPrimaryKeyValue> const& other) const noexcept
389 {
390 return _referencedFieldValue <=> other.Value();
391 }
392
393 /// Equality comparison operator.
394 bool operator==(BelongsTo const& other) const noexcept
395 {
396 return (_referencedFieldValue <=> other.Value()) == std::weak_ordering::equivalent;
397 }
398
399 /// Inequality comparison operator.
400 bool operator!=(BelongsTo const& other) const noexcept
401 {
402 return (_referencedFieldValue <=> other.Value()) != std::weak_ordering::equivalent;
403 }
404
405 /// Equality comparison operator with a Field.
406 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
407 bool operator==(Field<T, IsPrimaryKeyValue> const& other) const noexcept
408 {
409 return (_referencedFieldValue <=> other.Value()) == std::weak_ordering::equivalent;
410 }
411
412 /// Inequality comparison operator with a Field.
413 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
414 bool operator!=(Field<T, IsPrimaryKeyValue> const& other) const noexcept
415 {
416 return (_referencedFieldValue <=> other.Value()) != std::weak_ordering::equivalent;
417 }
418
419 /// Carries the deferred load, installed by the DataMapper.
420 struct Loader
421 {
422 /// Loads the referenced record: @ref RelationError::NotFound when there is none.
423 std::function<RelationResult<ReferencedRecord>()> loadReference {};
424 };
425
426 /// Used internally to configure on-demand loading of the record.
427 void SetAutoLoader(Loader loader) noexcept
428 {
429 _loader = std::move(loader);
430 }
431
432 private:
433 /// Loads the record unless it already is, or its unavailability is already known.
434 ///
435 /// @ref RelationError::NotFound and @ref RelationError::Outdated are remembered - neither changes by
436 /// asking again - until the foreign key is re-pointed; a failed query is retried on the next access.
437 [[nodiscard]] RelationResult<void> Load() const
438 {
439 if (_loaded)
440 return {};
441 if (_loadError)
442 return std::unexpected { *_loadError };
443 if constexpr (IsOptional)
444 if (!_referencedFieldValue)
445 return std::unexpected { RelationError::NotFound };
446 if (!_loader.loadReference)
447 return std::unexpected { RelationError::NotConfigured };
448
449 auto loaded = _loader.loadReference();
450 if (!loaded)
451 {
452 if (loaded.error() != RelationError::QueryFailed)
453 _loadError = loaded.error();
454 return std::unexpected { loaded.error() };
455 }
456 _record = std::make_unique<ReferencedRecord>(std::move(*loaded));
457 _loaded = true;
458 return {};
459 }
460
461 void LoadOrThrow() const
462 {
463 if (auto const loaded = Load(); !loaded)
464 throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>, loaded.error());
465 }
466
467 ValueType _referencedFieldValue {};
468 Loader _loader {};
469 mutable bool _loaded = false;
470 bool _modified = false;
471 mutable std::unique_ptr<ReferencedRecord> _record {};
472 mutable std::optional<RelationError> _loadError {};
473};
474
475template <auto ReferencedField, auto ColumnNameOverrideString, SqlNullable Nullable>
476std::ostream& operator<<(std::ostream& os, BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable> const& belongsTo)
477{
478 return os << belongsTo.Value();
479}
480
481namespace detail
482{
483 template <typename T>
484 struct IsBelongsToType: std::false_type
485 {
486 };
487
488 template <auto ReferencedField, auto ColumnNameOverrideString, SqlNullable Nullable>
489 struct IsBelongsToType<BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable>>: std::true_type
490 {
491 };
492
493} // namespace detail
494
495template <typename T>
496constexpr bool IsBelongsTo = detail::IsBelongsToType<std::remove_cvref_t<T>>::value;
497
498template <typename T>
499concept is_belongs_to = IsBelongsTo<T>;
500
501template <typename T>
502constexpr bool IsOptionalBelongsTo = false;
503
504template <is_belongs_to T>
505constexpr bool IsOptionalBelongsTo<T> = T::IsOptional;
506
507template <auto ReferencedField, auto ColumnNameOverrideString, SqlNullable Nullable>
508struct SqlDataBinder<BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable>>
509{
510 using SelfType = BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable>;
511 using InnerType = SelfType::ValueType;
512
513 static constexpr auto ColumnType = SqlDataBinder<InnerType>::ColumnType;
514
515 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
516 SQLUSMALLINT column,
517 SelfType const& value,
518 SqlDataBinderCallback& cb)
519 {
520 return SqlDataBinder<InnerType>::InputParameter(stmt, column, value.Value(), cb);
521 }
522
523 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
524 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, SelfType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
525 {
526 auto const sqlReturn = SqlDataBinder<InnerType>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
527 cb.PlanPostProcessOutputColumn([result]() { result->SetModified(true); });
528 return sqlReturn;
529 }
530
531 /// @throws Whatever `SqlDataBinder<InnerType>::GetColumn` throws — this forwards to an arbitrary
532 /// binder and cannot promise more than the one it wraps.
533 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
534 GetColumn(SQLHSTMT stmt, SQLUSMALLINT column, SelfType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb)
535 {
536 auto const sqlReturn = SqlDataBinder<InnerType>::GetColumn(stmt, column, &result->MutableValue(), indicator, cb);
537 if (SQL_SUCCEEDED(sqlReturn))
538 result->SetModified(true);
539 return sqlReturn;
540 }
541};
542
543} // namespace Lightweight
Represents the many-to-one side of a foreign-key relationship.
Definition BelongsTo.hpp:63
BelongsTo & operator=(BelongsTo &&other) noexcept
Move assignment operator.
LIGHTWEIGHT_FORCE_INLINE void AdoptFetchedRecord(std::optional< ReferencedRecord > record)
static constexpr auto IsAutoIncrementPrimaryKey
Indicates that a BelongsTo field is never an auto-increment primary key.
bool operator==(BelongsTo const &other) const noexcept
Equality comparison operator.
std::weak_ordering operator<=>(Field< T, IsPrimaryKeyValue > const &other) const noexcept
Three-way comparison operator with a Field.
bool operator==(Field< T, IsPrimaryKeyValue > const &other) const noexcept
Equality comparison operator with a Field.
std::weak_ordering operator<=>(BelongsTo const &other) const noexcept
Three-way comparison operator.
bool operator!=(BelongsTo const &other) const noexcept
Inequality comparison operator.
BelongsTo & operator=(SqlNullType) noexcept
Assigns NULL to the relationship, clearing the loaded record.
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:93
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & EmplaceRecord()
Emplaces a record into the relationship. This will mark the relationship as loaded.
constexpr BelongsTo(S &&... value) noexcept
Constructs a new BelongsTo with the given value(s) forwarded to the underlying value type.
static constexpr auto ReferencedField
The field in the other record that references the current record.
Definition BelongsTo.hpp:66
static constexpr auto IsMandatory
Indicates whether this relationship is mandatory (non-nullable).
LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept
Checks if the field value is NULL.
static constexpr auto IsPrimaryKey
Indicates that a BelongsTo field is never a primary key.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord * LoadedRecord() noexcept
Returns the already-loaded referenced record, or nullptr when none is loaded.
BelongsTo & operator=(BelongsTo const &other)
Copy assignment operator.
LIGHTWEIGHT_FORCE_INLINE void BindOutputColumn(SQLSMALLINT outputIndex, Stmt &stmt)
Binds the foreign key value to the given output column index on the statement.
LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept
Checks if the field is modified.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord * operator->(this Self &&self)
Retrieves the referenced record. Only available when the relationship is mandatory.
constexpr BelongsTo(BelongsTo &&other) noexcept
Move constructor.
LIGHTWEIGHT_FORCE_INLINE constexpr ValueType & MutableValue() noexcept
Retrieves the mutable reference to the value of the field.
BelongsTo & operator=(ReferencedRecord &other)
Assigns a referenced record, updating the foreign key and loaded state.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const * LoadedRecord() const noexcept
Returns the already-loaded referenced record, or nullptr when none is loaded.
LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const & Value() const noexcept
Retrieves the reference to the value of the field.
constexpr BelongsTo(BelongsTo const &other) noexcept
Copy constructor.
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:98
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the record.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & operator*(this Self &&self)
Retrieves the referenced record. Only available when the relationship is mandatory.
constexpr BelongsTo(ReferencedRecord const &other) noexcept
Constructs a new BelongsTo from the given referenced record, copying its primary key.
LIGHTWEIGHT_FORCE_INLINE constexpr auto Record(this Self &&self) -> RelationResult< std::reference_wrapper< std::conditional_t< std::is_const_v< std::remove_reference_t< Self > >, ReferencedRecord const, ReferencedRecord > > >
Retrieves the referenced record, loading it on first access.
MemberClassType< decltype(TheReferencedField)> ReferencedRecord
Represents the record type of the other field.
Definition BelongsTo.hpp:87
static constexpr auto IsOptional
Indicates whether this relationship is optional (nullable).
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:69
bool operator!=(Field< T, IsPrimaryKeyValue > const &other) const noexcept
Inequality comparison operator with a Field.
std::expected< T, RelationError > RelationResult
The outcome of a relation access: the requested value, or why it is unavailable.
Definition Error.hpp:44
@ NotFound
There is nothing to load: the foreign key is NULL, or the referenced row does not exist.
Carries the deferred load, installed by the DataMapper.
std::function< RelationResult< ReferencedRecord >()> loadReference
Loads the referenced record: RelationError::NotFound when there is none.
Represents a single column in a table.
Definition Field.hpp:154