Lightweight 0.20260213.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 "../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 <optional>
19#include <string_view>
20#include <type_traits>
21
22namespace Lightweight
23{
24
25/// @brief Helper function to use with std::optional<std::reference_wrapper<T>>
26/// like this .transform(Unwrap).value_or({})
27auto inline Unwrap = [](auto v) {
28 return v.get();
29};
30
31/// @brief Represents a one-to-one relationship.
32///
33/// The `TheReferencedField` parameter is the field in the other record that references the current record,
34/// in the form of `&OtherRecord::Field`.
35/// Other Field must be a primary key.
36///
37/// @tparam TheReferencedField The field in the other record that references the current record.
38/// @tparam ColumnNameOverrideString If not an empty string, this value will be used as the column name in the database.
39///
40/// @ingroup DataMapper
41///
42/// @code
43/// struct User {
44/// Field<SqlGuid, PrimaryKey::AutoAssign> id;
45/// Field<SqlAnsiString<30>> name;
46/// };
47/// struct Email {
48/// Field<SqlGuid, PrimaryKey::AutoAssign> id;
49/// Field<SqlAnsiString<40>> address;
50/// BelongsTo<&User::id> user;
51/// // Also possible to customize the column name
52/// BelongsTo<&User::id, SqlRealName<"the_user_id">, SqlNullable::Null> maybe_user;
53/// };
54/// @endcode
55template <auto TheReferencedField, auto ColumnNameOverrideString = std::nullopt, SqlNullable Nullable = SqlNullable::NotNull>
57{
58 public:
59 /// The field in the other record that references the current record.
60 static constexpr auto ReferencedField = TheReferencedField;
61
62 /// If not an empty string, this value will be used as the column name in the database.
63 static constexpr std::string_view ColumnNameOverride = []() consteval {
64 if constexpr (!std::same_as<decltype(ColumnNameOverrideString), std::nullopt_t>)
65 return std::string_view { ColumnNameOverrideString };
66 else
67 return std::string_view {};
68 }();
69
70#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
71 /// Represents the record type of the other field.
72 using ReferencedRecord = MemberClassType<TheReferencedField>;
73
74 /// Represents the base column type of the foreign key, matching the primary key of the other record.
75 using BaseType = typename[:std::meta::type_of(ReferencedField):] ::ValueType;
76
77 static_assert(std::remove_cvref_t<decltype(std::declval<ReferencedRecord>().[:ReferencedField:])>::IsPrimaryKey,
78 "The referenced field must be a primary key.");
79#else
80 /// Represents the record type of the other field.
81 using ReferencedRecord = MemberClassType<decltype(TheReferencedField)>;
82
83 static_assert(std::remove_cvref_t<decltype(std::declval<ReferencedRecord>().*ReferencedField)>::IsPrimaryKey,
84 "The referenced field must be a primary key.");
85
86 /// Represents the base column type of the foreign key, matching the primary key of the other record.
87 using BaseType = std::remove_cvref_t<decltype(std::declval<ReferencedRecord>().*ReferencedField)>::ValueType;
88#endif
89
90 /// Represents the value type of the foreign key,
91 /// which can be either an optional or a non-optional type of the referenced field,
92 using ValueType = std::conditional_t<Nullable == SqlNullable::Null, std::optional<BaseType>, BaseType>;
93
94 /// Indicates whether this relationship is optional (nullable).
95 static constexpr auto IsOptional = Nullable == SqlNullable::Null;
96 /// Indicates whether this relationship is mandatory (non-nullable).
97 static constexpr auto IsMandatory = !IsOptional;
98 /// Indicates that a BelongsTo field is never a primary key.
99 static constexpr auto IsPrimaryKey = false;
100 /// Indicates that a BelongsTo field is never an auto-increment primary key.
101 static constexpr auto IsAutoIncrementPrimaryKey = false;
102
103 /// Constructs a new BelongsTo with the given value(s) forwarded to the underlying value type.
104 template <typename... S>
105 requires std::constructible_from<ValueType, S...>
106 constexpr BelongsTo(S&&... value) noexcept:
107 _referencedFieldValue(std::forward<S>(value)...)
108 {
109 }
110
111 /// Constructs a new BelongsTo from the given referenced record, copying its primary key.
112 constexpr BelongsTo(ReferencedRecord const& other) noexcept:
113#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
114 _referencedFieldValue { (other.[:ReferencedField:]).Value() },
115#else
116 _referencedFieldValue { (other.*ReferencedField).Value() },
117#endif
118 _loaded { true },
119 _record { std::make_unique<ReferencedRecord>(other) }
120 {
121 }
122
123 /// Copy constructor.
124 constexpr BelongsTo(BelongsTo const& other) noexcept:
125 _referencedFieldValue(other._referencedFieldValue),
126 _loader(std::move(other._loader)),
127 _loaded(other._loaded),
128 _modified(other._modified),
129 _record(other._record ? std::make_unique<ReferencedRecord>(*other._record) : nullptr)
130 {
131 }
132
133 /// Move constructor.
134 constexpr BelongsTo(BelongsTo&& other) noexcept:
135 _referencedFieldValue(std::move(other._referencedFieldValue)),
136 _loader(std::move(other._loader)),
137 _loaded(other._loaded),
138 _modified(other._modified),
139 _record(std::move(other._record))
140 {
141 }
142
143 /// Assigns NULL to the relationship, clearing the loaded record.
144 BelongsTo& operator=(SqlNullType /*nullValue*/) noexcept
145 {
146 if (!_referencedFieldValue)
147 return *this;
148 _loaded = false;
149 _record = std::nullopt;
150 _referencedFieldValue = {};
151 _modified = true;
152 return *this;
153 }
154
155 /// Assigns a referenced record, updating the foreign key and loaded state.
157 {
158#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
159 if (_referencedFieldValue == (other.[:ReferencedField:]).Value())
160#else
161 if (_referencedFieldValue == (other.*ReferencedField).Value())
162#endif
163 return *this;
164 _loaded = true;
165 _record = std::make_unique<ReferencedRecord>(other);
166#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
167 _referencedFieldValue = (other.[:ReferencedField:]).Value();
168#else
169 _referencedFieldValue = (other.*ReferencedField).Value();
170#endif
171 _modified = true;
172 return *this;
173 }
174
175 /// Copy assignment operator.
177 {
178 if (this == &other)
179 return *this;
180
181 _referencedFieldValue = other._referencedFieldValue;
182 _loader = std::move(other._loader);
183 _loaded = other._loaded;
184 _modified = other._modified;
185 _record = other._record ? std::make_unique<ReferencedRecord>(*other._record) : nullptr;
186
187 return *this;
188 }
189
190 /// Move assignment operator.
191 BelongsTo& operator=(BelongsTo&& other) noexcept
192 {
193 if (this == &other)
194 return *this;
195 _referencedFieldValue = std::move(other._referencedFieldValue);
196 _loader = std::move(other._loader);
197 _loaded = other._loaded;
198 _modified = other._modified;
199 _record = std::move(other._record);
200 other._loaded = false;
201 return *this;
202 }
203
204 ~BelongsTo() noexcept = default;
205
206 /// Marks the field as modified or unmodified.
207 LIGHTWEIGHT_FORCE_INLINE constexpr void SetModified(bool value) noexcept
208 {
209 _modified = value;
210 }
211
212 /// Checks if the field is modified.
213 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept
214 {
215 return _modified;
216 }
217
218 /// Retrieves the reference to the value of the field.
219 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const& Value() const noexcept
220 {
221 return _referencedFieldValue;
222 }
223
224 /// Retrieves the mutable reference to the value of the field.
225 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType& MutableValue() noexcept
226 {
227 return _referencedFieldValue;
228 }
229
230 // NOLINTBEGIN(cppcoreguidelines-missing-std-forward)
231
232 /// Retrieves a record from the relationship. When the record is not optional
233 template <typename Self>
234 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& Record(this Self&& self)
235 requires(IsMandatory)
236 {
237 self.RequireLoaded();
238 return *self._record;
239 }
240
241 /// Retrieves a record from the relationship. When the record is optional
242 /// we return object similar to std::optional<ReferencedRecord&>
243 template <typename Self>
244 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr decltype(auto) Record(this Self&& self)
245 requires(IsOptional)
246 {
247 self.RequireLoaded();
248 return [&]() -> std::optional<std::reference_wrapper<ReferencedRecord>> {
249 if (self._record)
250 return *self._record;
251 return std::nullopt;
252 }();
253 // .transform([](auto v) { return v.get(); });
254 // requires at least clang-20
255 }
256
257 /// Retrieves the record from the relationship.
258 /// Only available when the relationship is mandatory.
259 template <typename Self>
260 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& operator*(this Self&& self) noexcept
261 requires(IsMandatory)
262 {
263 self.RequireLoaded();
264 return *self._record;
265 }
266
267 /// Retrieves the record from the relationship.
268 /// Only available when the relationship is mandatory.
269 template <typename Self>
270 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* operator->(this Self&& self)
271 requires(IsMandatory)
272 {
273 self.RequireLoaded();
274 return self._record.get();
275 }
276
277 // NOLINTEND(cppcoreguidelines-missing-std-forward)
278
279 /// Checks if the field value is NULL.
280 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept
281 {
282 return !_referencedFieldValue;
283 }
284
285 /// Checks if the field value is not NULL.
286 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr explicit operator bool() const noexcept
287 {
288 return static_cast<bool>(_referencedFieldValue);
289 }
290
291 /// Emplaces a record into the relationship. This will mark the relationship as loaded.
292 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& EmplaceRecord()
293 {
294 _loaded = true;
295 _record = std::make_unique<ReferencedRecord>();
296 return *_record;
297 }
298
299 /// Binds the foreign key value to the given output column index on the statement.
300 template <typename Stmt>
301 LIGHTWEIGHT_FORCE_INLINE void BindOutputColumn(SQLSMALLINT outputIndex, Stmt& stmt)
302 {
303 stmt.BindOutputColumn(outputIndex, &_referencedFieldValue);
304 }
305
306 /// Three-way comparison operator.
307 std::weak_ordering operator<=>(BelongsTo const& other) const noexcept
308 {
309 return _referencedFieldValue <=> other.Value();
310 }
311
312 /// Three-way comparison operator with a Field.
313 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
314 std::weak_ordering operator<=>(Field<T, IsPrimaryKeyValue> const& other) const noexcept
315 {
316 return _referencedFieldValue <=> other.Value();
317 }
318
319 /// Equality comparison operator.
320 bool operator==(BelongsTo const& other) const noexcept
321 {
322 return (_referencedFieldValue <=> other.Value()) == std::weak_ordering::equivalent;
323 }
324
325 /// Inequality comparison operator.
326 bool operator!=(BelongsTo const& other) const noexcept
327 {
328 return (_referencedFieldValue <=> other.Value()) != std::weak_ordering::equivalent;
329 }
330
331 /// Equality comparison operator with a Field.
332 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
333 bool operator==(Field<T, IsPrimaryKeyValue> const& other) const noexcept
334 {
335 return (_referencedFieldValue <=> other.Value()) == std::weak_ordering::equivalent;
336 }
337
338 /// Inequality comparison operator with a Field.
339 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
340 bool operator!=(Field<T, IsPrimaryKeyValue> const& other) const noexcept
341 {
342 return (_referencedFieldValue <=> other.Value()) != std::weak_ordering::equivalent;
343 }
344
345 struct Loader
346 {
347 std::function<std::optional<ReferencedRecord>()> loadReference {};
348 };
349
350 /// Used internally to configure on-demand loading of the record.
351 void SetAutoLoader(Loader loader) noexcept
352 {
353 _loader = std::move(loader);
354 }
355
356 private:
357 void RequireLoaded() const
358 {
359 if (_loaded)
360 return;
361
362 if (_loader.loadReference)
363 {
364 auto value = _loader.loadReference();
365 if (value)
366 {
367 _record = std::make_unique<ReferencedRecord>(std::move(value.value()));
368 _loaded = true;
369 }
370 }
371
372 if constexpr (IsMandatory)
373 if (!_loaded)
374 throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>);
375 }
376
377 ValueType _referencedFieldValue {};
378 Loader _loader {};
379 mutable bool _loaded = false;
380 bool _modified = false;
381 mutable std::unique_ptr<ReferencedRecord> _record {};
382};
383
384template <auto ReferencedField, auto ColumnNameOverrideString, SqlNullable Nullable>
385std::ostream& operator<<(std::ostream& os, BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable> const& belongsTo)
386{
387 return os << belongsTo.Value();
388}
389
390namespace detail
391{
392 template <typename T>
393 struct IsBelongsToType: std::false_type
394 {
395 };
396
397 template <auto ReferencedField, auto ColumnNameOverrideString, SqlNullable Nullable>
398 struct IsBelongsToType<BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable>>: std::true_type
399 {
400 };
401
402} // namespace detail
403
404template <typename T>
405constexpr bool IsBelongsTo = detail::IsBelongsToType<std::remove_cvref_t<T>>::value;
406
407template <typename T>
408concept is_belongs_to = IsBelongsTo<T>;
409
410template <typename T>
411constexpr bool IsOptionalBelongsTo = false;
412
413template <is_belongs_to T>
414constexpr bool IsOptionalBelongsTo<T> = T::IsOptional;
415
416template <auto ReferencedField, auto ColumnNameOverrideString, SqlNullable Nullable>
417struct SqlDataBinder<BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable>>
418{
419 using SelfType = BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable>;
420 using InnerType = SelfType::ValueType;
421
422 static constexpr auto ColumnType = SqlDataBinder<InnerType>::ColumnType;
423
424 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
425 SQLUSMALLINT column,
426 SelfType const& value,
427 SqlDataBinderCallback& cb)
428 {
429 return SqlDataBinder<InnerType>::InputParameter(stmt, column, value.Value(), cb);
430 }
431
432 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
433 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, SelfType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
434 {
435 auto const sqlReturn = SqlDataBinder<InnerType>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
436 cb.PlanPostProcessOutputColumn([result]() { result->SetModified(true); });
437 return sqlReturn;
438 }
439
440 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(
441 SQLHSTMT stmt, SQLUSMALLINT column, SelfType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept
442 {
443 auto const sqlReturn = SqlDataBinder<InnerType>::GetColumn(stmt, column, &result->MutableValue(), indicator, cb);
444 if (SQL_SUCCEEDED(sqlReturn))
445 result->SetModified(true);
446 return sqlReturn;
447 }
448};
449
450} // namespace Lightweight
Represents a one-to-one relationship.
Definition BelongsTo.hpp:57
BelongsTo & operator=(BelongsTo &&other) noexcept
Move assignment operator.
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:87
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.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & operator*(this Self &&self) noexcept
static constexpr auto ReferencedField
The field in the other record that references the current record.
Definition BelongsTo.hpp:60
static constexpr auto IsMandatory
Indicates whether this relationship is mandatory (non-nullable).
Definition BelongsTo.hpp:97
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.
Definition BelongsTo.hpp:99
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)
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 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:92
LIGHTWEIGHT_FORCE_INLINE constexpr decltype(auto) Record(this Self &&self)
void SetAutoLoader(Loader loader) noexcept
Used internally to configure on-demand loading of the record.
constexpr BelongsTo(ReferencedRecord const &other) noexcept
Constructs a new BelongsTo from the given referenced record, copying its primary key.
MemberClassType< decltype(TheReferencedField)> ReferencedRecord
Represents the record type of the other field.
Definition BelongsTo.hpp:81
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const & Record(this Self &&self)
Retrieves a record from the relationship. When the record is not optional.
static constexpr auto IsOptional
Indicates whether this relationship is optional (nullable).
Definition BelongsTo.hpp:95
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:63
bool operator!=(Field< T, IsPrimaryKeyValue > const &other) const noexcept
Inequality comparison operator with a Field.
Represents a single column in a table.
Definition Field.hpp:84