Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 71 additions & 24 deletions src/Lightweight/DataMapper/BelongsTo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
namespace Lightweight
{


/// @brief Helper function to use with std::optional<std::reference_wrapper<T>>
/// like this .transform(Unwrap).value_or({})
auto inline Unwrap = [](auto v) { return v.get(); };

/// @brief Represents a one-to-one relationship.
///
/// The `TheReferencedField` parameter is the field in the other record that references the current record,
Expand Down Expand Up @@ -180,45 +185,86 @@ class BelongsTo

~BelongsTo() noexcept = default;

// clang-format off

/// Marks the field as modified or unmodified.
LIGHTWEIGHT_FORCE_INLINE constexpr void SetModified(bool value) noexcept { _modified = value; }
LIGHTWEIGHT_FORCE_INLINE constexpr void SetModified(bool value) noexcept
{
_modified = value;
}

/// Checks if the field is modified.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept { return _modified; }
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsModified() const noexcept
{
return _modified;
}

/// Retrieves the reference to the value of the field.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const& Value() const noexcept { return _referencedFieldValue; }
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType const& Value() const noexcept
{
return _referencedFieldValue;
}

/// Retrieves the mutable reference to the value of the field.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType& MutableValue() noexcept { return _referencedFieldValue; }

/// Retrieves a record from the relationship.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& Record() { RequireLoaded(); return *_record; }

/// Retrieves an immutable reference to the record from the relationship.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& Record() const { RequireLoaded(); return *_record; }
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ValueType& MutableValue() noexcept
{
return _referencedFieldValue;
}

/// Retrieves the record from the relationship.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& operator*() noexcept { RequireLoaded(); return *_record; }
/// Retrieves a record from the relationship. When the record is not optional
template <typename Self>
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& Record(this Self&& self)
requires(IsMandatory)
{
self.RequireLoaded();
return *self._record;
}

/// Retrieves the record from the relationship.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& operator*() const noexcept { RequireLoaded(); return *_record; }
/// Retrieves a record from the relationship. When the record is optional
/// we return object similar to std::optional<ReferencedRecord&>
template <typename Self>
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr decltype(auto) Record(this Self&& self)
requires(IsOptional)
{
self.RequireLoaded();
return [&]() -> std::optional<std::reference_wrapper<ReferencedRecord>> {
if (self._record)
return *self._record;
return std::nullopt;
}();
// .transform([](auto v) { return v.get(); });
// requires at least clang-20
}

/// Retrieves the record from the relationship.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* operator->() { RequireLoaded(); return _record.get(); }
/// Only available when the relationship is mandatory.
template <typename Self>
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& operator*(this Self&& self) noexcept
requires(IsMandatory)
{
self.RequireLoaded();
return *self._record;
}

/// Retrieves the record from the relationship.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const* operator->() const { RequireLoaded(); return _record.get(); }
/// Only available when the relationship is mandatory.
template <typename Self>
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* operator->(this Self&& self)
requires(IsMandatory)
{
self.RequireLoaded();
return self._record.get();
}

/// Checks if the field value is NULL.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept { return !_referencedFieldValue; }
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool operator!() const noexcept
{
return !_referencedFieldValue;
}

/// Checks if the field value is not NULL.
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr explicit operator bool() const noexcept { return static_cast<bool>(_referencedFieldValue); }

// clang-format on
[[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr explicit operator bool() const noexcept
{
return static_cast<bool>(_referencedFieldValue);
}

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

if (!_loaded)
throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>);
if constexpr (IsMandatory)
if (!_loaded)
throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>>);
}

ValueType _referencedFieldValue {};
Expand Down
9 changes: 6 additions & 3 deletions src/examples/test_chinook/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,16 @@ int main()
for (auto& track: dm.Query<Track>().All())
{
dm.ConfigureRelationAutoLoading(track);
// NOLINTBEGIN(bugprone-unchecked-optional-access)
// BelogsTo relation loading
Log("Track Name: {}. Media type: {}. Genre: {}. Album id: {}. Artist name: {}",
toString(track.Name.Value().ToStringView()),
toString(track.MediaTypeId->Name.ValueOr(u"").ToStringView()),
toString(track.GenreId->Name.ValueOr(u"").ToStringView()),
toString(track.AlbumId->Title.Value().ToStringView()),
toString(track.AlbumId->ArtistId->Name.ValueOr(u"").ToStringView()));
toString(track.GenreId.Record().transform(Light::Unwrap).value().Name.ValueOr(u"").ToStringView()),
toString(track.AlbumId.Record().transform(Light::Unwrap).value().Title.Value().ToStringView()),
toString(
track.AlbumId.Record().transform(Light::Unwrap).value().ArtistId->Name.ValueOr(u"").ToStringView()));
// NOLINTEND(bugprone-unchecked-optional-access)
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/tests/DataMapper/CreateTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "../Utils.hpp"
#include "Entities.hpp"
#include "Lightweight/DataMapper/QueryBuilders.hpp"

#include <Lightweight/Lightweight.hpp>

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

auto nullableFKUserNotSet = NullableForeignKeyUser {};
dm.Create(nullableFKUserNotSet);
dm.Create<Light::DataMapperOptions{.loadRelations = false}>(nullableFKUserNotSet);
REQUIRE(!nullableFKUserNotSet.user.Value().has_value());
REQUIRE(!nullableFKUserNotSet.user.Record().has_value());
}


// NOLINTEND(bugprone-unchecked-optional-access)
20 changes: 20 additions & 0 deletions src/tests/DataMapper/RelationTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -600,4 +600,24 @@ TEST_CASE_METHOD(SqlTestFixture, "Table with aliased column names", "[DataMapper
}
}

TEST_CASE_METHOD(SqlTestFixture, "BelongsTo Optinal records", "[DataMapper]")
{
auto dm = DataMapper();

dm.CreateTables<User, NullableForeignKeyUser>();

auto user = User { .id = SqlGuid::Create(), .name = "John Doe" };
dm.Create(user);

auto nullableFKUser = NullableForeignKeyUser { .user = user };
dm.Create(nullableFKUser);
REQUIRE(nullableFKUser.user.Value().has_value());
REQUIRE(nullableFKUser.user.Record().transform(Light::Unwrap).value().id == user.id);

auto nullableFKUserNotSet = NullableForeignKeyUser {};
dm.Create<Light::DataMapperOptions{.loadRelations = false}>(nullableFKUserNotSet);
REQUIRE(!nullableFKUserNotSet.user.Value().has_value());
REQUIRE(!nullableFKUserNotSet.user.Record().transform(Light::Unwrap).value_or(User{}).id.Value());
}

// NOLINTEND(bugprone-unchecked-optional-access)
Loading