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