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 "../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 static constexpr auto IsOptional = Nullable == SqlNullable::Null;
95 static constexpr auto IsMandatory = !IsOptional;
96 static constexpr auto IsPrimaryKey = false;
97 static constexpr auto IsAutoIncrementPrimaryKey = false;
98
99 template <typename... S>
100 requires std::constructible_from<ValueType, S...>
101 constexpr BelongsTo(S&&... value) noexcept:
102 _referencedFieldValue(std::forward<S>(value)...)
103 {
104 }
105
106 constexpr BelongsTo(ReferencedRecord const& other) noexcept:
107#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
108 _referencedFieldValue { (other.[:ReferencedField:]).Value() },
109#else
110 _referencedFieldValue { (other.*ReferencedField).Value() },
111#endif
112 _loaded { true },
113 _record { std::make_unique<ReferencedRecord>(other) }
114 {
115 }
116
117 constexpr BelongsTo(BelongsTo const& other) noexcept:
118 _referencedFieldValue(other._referencedFieldValue),
119 _loader(std::move(other._loader)),
120 _loaded(other._loaded),
121 _modified(other._modified),
122 _record(other._record ? std::make_unique<ReferencedRecord>(*other._record) : nullptr)
123 {
124 }
125
126 constexpr BelongsTo(BelongsTo&& other) noexcept:
127 _referencedFieldValue(std::move(other._referencedFieldValue)),
128 _loader(std::move(other._loader)),
129 _loaded(other._loaded),
130 _modified(other._modified),
131 _record(std::move(other._record))
132 {
133 }
134
135 BelongsTo& operator=(SqlNullType /*nullValue*/) noexcept
136 {
137 if (!_referencedFieldValue)
138 return *this;
139 _loaded = false;
140 _record = std::nullopt;
141 _referencedFieldValue = {};
142 _modified = true;
143 return *this;
144 }
145
146 BelongsTo& operator=(ReferencedRecord& other)
147 {
148#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
149 if (_referencedFieldValue == (other.[:ReferencedField:]).Value())
150#else
151 if (_referencedFieldValue == (other.*ReferencedField).Value())
152#endif
153 return *this;
154 _loaded = true;
155 _record = std::make_unique<ReferencedRecord>(other);
156#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
157 _referencedFieldValue = (other.[:ReferencedField:]).Value();
158#else
159 _referencedFieldValue = (other.*ReferencedField).Value();
160#endif
161 _modified = true;
162 return *this;
163 }
164
165 BelongsTo& operator=(BelongsTo const& other)
166 {
167 if (this == &other)
168 return *this;
169
170 _referencedFieldValue = other._referencedFieldValue;
171 _loader = std::move(other._loader);
172 _loaded = other._loaded;
173 _modified = other._modified;
174 _record = other._record ? std::make_unique<ReferencedRecord>(*other._record) : nullptr;
175
176 return *this;
177 }
178
179 BelongsTo& operator=(BelongsTo&& other) noexcept
180 {
181 if (this == &other)
182 return *this;
183 _referencedFieldValue = std::move(other._referencedFieldValue);
184 _loader = std::move(other._loader);
185 _loaded = other._loaded;
186 _modified = other._modified;
187 _record = std::move(other._record);
188 other._loaded = false;
189 return *this;
190 }
191
192 ~BelongsTo() noexcept = default;
193
194 /// Marks the field as modified or unmodified.
195 LIGHTWEIGHT_FORCE_INLINE constexpr void SetModified(bool value) noexcept
196 {
197 _modified = value;
198 }
199
200 /// Checks if the field is modified.
201 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept
202 {
203 return _modified;
204 }
205
206 /// Retrieves the reference to the value of the field.
207 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const& Value() const noexcept
208 {
209 return _referencedFieldValue;
210 }
211
212 /// Retrieves the mutable reference to the value of the field.
213 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType& MutableValue() noexcept
214 {
215 return _referencedFieldValue;
216 }
217
218 // NOLINTBEGIN(cppcoreguidelines-missing-std-forward)
219
220 /// Retrieves a record from the relationship. When the record is not optional
221 template <typename Self>
222 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& Record(this Self&& self)
223 requires(IsMandatory)
224 {
225 self.RequireLoaded();
226 return *self._record;
227 }
228
229 /// Retrieves a record from the relationship. When the record is optional
230 /// we return object similar to std::optional<ReferencedRecord&>
231 template <typename Self>
232 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr decltype(auto) Record(this Self&& self)
233 requires(IsOptional)
234 {
235 self.RequireLoaded();
236 return [&]() -> std::optional<std::reference_wrapper<ReferencedRecord>> {
237 if (self._record)
238 return *self._record;
239 return std::nullopt;
240 }();
241 // .transform([](auto v) { return v.get(); });
242 // requires at least clang-20
243 }
244
245 /// Retrieves the record from the relationship.
246 /// Only available when the relationship is mandatory.
247 template <typename Self>
248 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& operator*(this Self&& self) noexcept
249 requires(IsMandatory)
250 {
251 self.RequireLoaded();
252 return *self._record;
253 }
254
255 /// Retrieves the record from the relationship.
256 /// Only available when the relationship is mandatory.
257 template <typename Self>
258 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* operator->(this Self&& self)
259 requires(IsMandatory)
260 {
261 self.RequireLoaded();
262 return self._record.get();
263 }
264
265 // NOLINTEND(cppcoreguidelines-missing-std-forward)
266
267 /// Checks if the field value is NULL.
268 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept
269 {
270 return !_referencedFieldValue;
271 }
272
273 /// Checks if the field value is not NULL.
274 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr explicit operator bool() const noexcept
275 {
276 return static_cast<bool>(_referencedFieldValue);
277 }
278
279 /// Emplaces a record into the relationship. This will mark the relationship as loaded.
280 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& EmplaceRecord()
281 {
282 _loaded = true;
283 _record = std::make_unique<ReferencedRecord>();
284 return *_record;
285 }
286
287 template <typename Stmt>
288 LIGHTWEIGHT_FORCE_INLINE void BindOutputColumn(SQLSMALLINT outputIndex, Stmt& stmt)
289 {
290 stmt.BindOutputColumn(outputIndex, &_referencedFieldValue);
291 }
292
293 std::weak_ordering operator<=>(BelongsTo const& other) const noexcept
294 {
295 return _referencedFieldValue <=> other.Value();
296 }
297
298 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
299 std::weak_ordering operator<=>(Field<T, IsPrimaryKeyValue> const& other) const noexcept
300 {
301 return _referencedFieldValue <=> other.Value();
302 }
303
304 bool operator==(BelongsTo const& other) const noexcept
305 {
306 return (_referencedFieldValue <=> other.Value()) == std::weak_ordering::equivalent;
307 }
308
309 bool operator!=(BelongsTo const& other) const noexcept
310 {
311 return (_referencedFieldValue <=> other.Value()) != std::weak_ordering::equivalent;
312 }
313
314 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
315 bool operator==(Field<T, IsPrimaryKeyValue> const& other) const noexcept
316 {
317 return (_referencedFieldValue <=> other.Value()) == std::weak_ordering::equivalent;
318 }
319
320 template <detail::FieldElementType T, PrimaryKey IsPrimaryKeyValue = PrimaryKey::No>
321 bool operator!=(Field<T, IsPrimaryKeyValue> const& other) const noexcept
322 {
323 return (_referencedFieldValue <=> other.Value()) != std::weak_ordering::equivalent;
324 }
325
326 struct Loader
327 {
328 std::function<std::optional<ReferencedRecord>()> loadReference {};
329 };
330
331 /// Used internally to configure on-demand loading of the record.
332 void SetAutoLoader(Loader loader) noexcept
333 {
334 _loader = std::move(loader);
335 }
336
337 private:
338 void RequireLoaded() const
339 {
340 if (_loaded)
341 return;
342
343 if (_loader.loadReference)
344 {
345 auto value = _loader.loadReference();
346 if (value)
347 {
348 _record = std::make_unique<ReferencedRecord>(std::move(value.value()));
349 _loaded = true;
350 }
351 }
352
353 if constexpr (IsMandatory)
354 if (!_loaded)
355 throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>);
356 }
357
358 ValueType _referencedFieldValue {};
359 Loader _loader {};
360 mutable bool _loaded = false;
361 bool _modified = false;
362 mutable std::unique_ptr<ReferencedRecord> _record {};
363};
364
365template <auto ReferencedField, auto ColumnNameOverrideString, SqlNullable Nullable>
366std::ostream& operator<<(std::ostream& os, BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable> const& belongsTo)
367{
368 return os << belongsTo.Value();
369}
370
371namespace detail
372{
373 template <typename T>
374 struct IsBelongsToType: std::false_type
375 {
376 };
377
378 template <auto ReferencedField, auto ColumnNameOverrideString, SqlNullable Nullable>
379 struct IsBelongsToType<BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable>>: std::true_type
380 {
381 };
382
383} // namespace detail
384
385template <typename T>
386constexpr bool IsBelongsTo = detail::IsBelongsToType<std::remove_cvref_t<T>>::value;
387
388template <typename T>
389concept is_belongs_to = IsBelongsTo<T>;
390
391template <typename T>
392constexpr bool IsOptionalBelongsTo = false;
393
394template <is_belongs_to T>
395constexpr bool IsOptionalBelongsTo<T> = T::IsOptional;
396
397template <auto ReferencedField, auto ColumnNameOverrideString, SqlNullable Nullable>
398struct SqlDataBinder<BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable>>
399{
400 using SelfType = BelongsTo<ReferencedField, ColumnNameOverrideString, Nullable>;
401 using InnerType = SelfType::ValueType;
402
403 static constexpr auto ColumnType = SqlDataBinder<InnerType>::ColumnType;
404
405 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN InputParameter(SQLHSTMT stmt,
406 SQLUSMALLINT column,
407 SelfType const& value,
408 SqlDataBinderCallback& cb)
409 {
410 return SqlDataBinder<InnerType>::InputParameter(stmt, column, value.Value(), cb);
411 }
412
413 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN
414 OutputColumn(SQLHSTMT stmt, SQLUSMALLINT column, SelfType* result, SQLLEN* indicator, SqlDataBinderCallback& cb)
415 {
416 auto const sqlReturn = SqlDataBinder<InnerType>::OutputColumn(stmt, column, &result->MutableValue(), indicator, cb);
417 cb.PlanPostProcessOutputColumn([result]() { result->SetModified(true); });
418 return sqlReturn;
419 }
420
421 static LIGHTWEIGHT_FORCE_INLINE SQLRETURN GetColumn(
422 SQLHSTMT stmt, SQLUSMALLINT column, SelfType* result, SQLLEN* indicator, SqlDataBinderCallback const& cb) noexcept
423 {
424 auto const sqlReturn = SqlDataBinder<InnerType>::GetColumn(stmt, column, &result->MutableValue(), indicator, cb);
425 if (SQL_SUCCEEDED(sqlReturn))
426 result->SetModified(true);
427 return sqlReturn;
428 }
429};
430
431} // namespace Lightweight
Represents a one-to-one relationship.
Definition BelongsTo.hpp:57
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.
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
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: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.
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 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