Skip to content

Commit aab2a87

Browse files
YaraslautYaraslau Tamashevich
authored andcommitted
Change BelongsTo Api to have a safe access to the elements when the entry is optinal
1 parent c5e172c commit aab2a87

File tree

4 files changed

+101
-30
lines changed

4 files changed

+101
-30
lines changed

src/Lightweight/DataMapper/BelongsTo.hpp

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
namespace Lightweight
1818
{
1919

20+
21+
/// @brief Helper function to use with std::optional<std::reference_wrapper<T>>
22+
/// like this .transform(Unwrap).value_or({})
23+
auto inline Unwrap = [](auto v) { return v.get(); };
24+
2025
/// @brief Represents a one-to-one relationship.
2126
///
2227
/// The `TheReferencedField` parameter is the field in the other record that references the current record,
@@ -180,45 +185,86 @@ class BelongsTo
180185

181186
~BelongsTo() noexcept = default;
182187

183-
// clang-format off
184-
185188
/// Marks the field as modified or unmodified.
186-
LIGHTWEIGHT_FORCE_INLINE constexpr void SetModified(bool value) noexcept { _modified = value; }
189+
LIGHTWEIGHT_FORCE_INLINE constexpr void SetModified(bool value) noexcept
190+
{
191+
_modified = value;
192+
}
187193

188194
/// Checks if the field is modified.
189-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept { return _modified; }
195+
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept
196+
{
197+
return _modified;
198+
}
190199

191200
/// Retrieves the reference to the value of the field.
192-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const& Value() const noexcept { return _referencedFieldValue; }
201+
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const& Value() const noexcept
202+
{
203+
return _referencedFieldValue;
204+
}
193205

194206
/// Retrieves the mutable reference to the value of the field.
195-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType& MutableValue() noexcept { return _referencedFieldValue; }
196-
197-
/// Retrieves a record from the relationship.
198-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& Record() { RequireLoaded(); return *_record; }
199-
200-
/// Retrieves an immutable reference to the record from the relationship.
201-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& Record() const { RequireLoaded(); return *_record; }
207+
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType& MutableValue() noexcept
208+
{
209+
return _referencedFieldValue;
210+
}
202211

203-
/// Retrieves the record from the relationship.
204-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& operator*() noexcept { RequireLoaded(); return *_record; }
212+
/// Retrieves a record from the relationship. When the record is not optional
213+
template <typename Self>
214+
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& Record(this Self&& self)
215+
requires(IsMandatory)
216+
{
217+
self.RequireLoaded();
218+
return *self._record;
219+
}
205220

206-
/// Retrieves the record from the relationship.
207-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& operator*() const noexcept { RequireLoaded(); return *_record; }
221+
/// Retrieves a record from the relationship. When the record is optional
222+
/// we return object similar to std::optional<ReferencedRecord&>
223+
template <typename Self>
224+
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr decltype(auto) Record(this Self&& self)
225+
requires(IsOptional)
226+
{
227+
self.RequireLoaded();
228+
return [&]() -> std::optional<std::reference_wrapper<ReferencedRecord>> {
229+
if (self._record)
230+
return *self._record;
231+
return std::nullopt;
232+
}();
233+
// .transform([](auto v) { return v.get(); });
234+
// requires at least clang-20
235+
}
208236

209237
/// Retrieves the record from the relationship.
210-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* operator->() { RequireLoaded(); return _record.get(); }
238+
/// Only available when the relationship is mandatory.
239+
template <typename Self>
240+
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& operator*(this Self&& self) noexcept
241+
requires(IsMandatory)
242+
{
243+
self.RequireLoaded();
244+
return *self._record;
245+
}
211246

212247
/// Retrieves the record from the relationship.
213-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const* operator->() const { RequireLoaded(); return _record.get(); }
248+
/// Only available when the relationship is mandatory.
249+
template <typename Self>
250+
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* operator->(this Self&& self)
251+
requires(IsMandatory)
252+
{
253+
self.RequireLoaded();
254+
return self._record.get();
255+
}
214256

215257
/// Checks if the field value is NULL.
216-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept { return !_referencedFieldValue; }
258+
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept
259+
{
260+
return !_referencedFieldValue;
261+
}
217262

218263
/// Checks if the field value is not NULL.
219-
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr explicit operator bool() const noexcept { return static_cast<bool>(_referencedFieldValue); }
220-
221-
// clang-format on
264+
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr explicit operator bool() const noexcept
265+
{
266+
return static_cast<bool>(_referencedFieldValue);
267+
}
222268

223269
/// Emplaces a record into the relationship. This will mark the relationship as loaded.
224270
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& EmplaceRecord()
@@ -293,8 +339,9 @@ class BelongsTo
293339
}
294340
}
295341

296-
if (!_loaded)
297-
throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>);
342+
if constexpr (IsMandatory)
343+
if (!_loaded)
344+
throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>);
298345
}
299346

300347
ValueType _referencedFieldValue {};

src/examples/test_chinook/main.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,16 @@ int main()
144144
for (auto& track: dm.Query<Track>().All())
145145
{
146146
dm.ConfigureRelationAutoLoading(track);
147+
// NOLINTBEGIN(bugprone-unchecked-optional-access)
147148
// BelogsTo relation loading
148149
Log("Track Name: {}. Media type: {}. Genre: {}. Album id: {}. Artist name: {}",
149150
toString(track.Name.Value().ToStringView()),
150151
toString(track.MediaTypeId->Name.ValueOr(u"").ToStringView()),
151-
toString(track.GenreId->Name.ValueOr(u"").ToStringView()),
152-
toString(track.AlbumId->Title.Value().ToStringView()),
153-
toString(track.AlbumId->ArtistId->Name.ValueOr(u"").ToStringView()));
152+
toString(track.GenreId.Record().transform(Light::Unwrap).value().Name.ValueOr(u"").ToStringView()),
153+
toString(track.AlbumId.Record().transform(Light::Unwrap).value().Title.Value().ToStringView()),
154+
toString(
155+
track.AlbumId.Record().transform(Light::Unwrap).value().ArtistId->Name.ValueOr(u"").ToStringView()));
156+
// NOLINTEND(bugprone-unchecked-optional-access)
154157
}
155158
}
156159

src/tests/DataMapper/CreateTests.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "../Utils.hpp"
55
#include "Entities.hpp"
6+
#include "Lightweight/DataMapper/QueryBuilders.hpp"
67

78
#include <Lightweight/Lightweight.hpp>
89

@@ -150,12 +151,12 @@ TEST_CASE_METHOD(SqlTestFixture, "Loading of the dependent records after create"
150151
auto nullableFKUser = NullableForeignKeyUser { .user = user };
151152
dm.Create(nullableFKUser);
152153
REQUIRE(nullableFKUser.user.Value().has_value());
153-
REQUIRE(nullableFKUser.user->id.Value() == user.id.Value());
154+
REQUIRE(nullableFKUser.user.Record().has_value());
154155

155156
auto nullableFKUserNotSet = NullableForeignKeyUser {};
156-
dm.Create(nullableFKUserNotSet);
157+
dm.Create<Light::DataMapperOptions{.loadRelations = false}>(nullableFKUserNotSet);
157158
REQUIRE(!nullableFKUserNotSet.user.Value().has_value());
159+
REQUIRE(!nullableFKUserNotSet.user.Record().has_value());
158160
}
159161

160-
161162
// NOLINTEND(bugprone-unchecked-optional-access)

src/tests/DataMapper/RelationTests.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,4 +600,24 @@ TEST_CASE_METHOD(SqlTestFixture, "Table with aliased column names", "[DataMapper
600600
}
601601
}
602602

603+
TEST_CASE_METHOD(SqlTestFixture, "BelongsTo Optinal records", "[DataMapper]")
604+
{
605+
auto dm = DataMapper();
606+
607+
dm.CreateTables<User, NullableForeignKeyUser>();
608+
609+
auto user = User { .id = SqlGuid::Create(), .name = "John Doe" };
610+
dm.Create(user);
611+
612+
auto nullableFKUser = NullableForeignKeyUser { .user = user };
613+
dm.Create(nullableFKUser);
614+
REQUIRE(nullableFKUser.user.Value().has_value());
615+
REQUIRE(nullableFKUser.user.Record().transform(Light::Unwrap).value().id == user.id);
616+
617+
auto nullableFKUserNotSet = NullableForeignKeyUser {};
618+
dm.Create<Light::DataMapperOptions{.loadRelations = false}>(nullableFKUserNotSet);
619+
REQUIRE(!nullableFKUserNotSet.user.Value().has_value());
620+
REQUIRE(!nullableFKUserNotSet.user.Record().transform(Light::Unwrap).value_or(User{}).id.Value());
621+
}
622+
603623
// NOLINTEND(bugprone-unchecked-optional-access)

0 commit comments

Comments
 (0)