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