diff --git a/src/Lightweight/DataMapper/BelongsTo.hpp b/src/Lightweight/DataMapper/BelongsTo.hpp index da629560..6dba6eec 100644 --- a/src/Lightweight/DataMapper/BelongsTo.hpp +++ b/src/Lightweight/DataMapper/BelongsTo.hpp @@ -17,6 +17,11 @@ namespace Lightweight { + +/// @brief Helper function to use with std::optional> +/// 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, @@ -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 + [[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 + template + [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr decltype(auto) Record(this Self&& self) + requires(IsOptional) + { + self.RequireLoaded(); + return [&]() -> std::optional> { + 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 + [[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 + [[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(_referencedFieldValue); } - - // clang-format on + [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr explicit operator bool() const noexcept + { + return static_cast(_referencedFieldValue); + } /// Emplaces a record into the relationship. This will mark the relationship as loaded. [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& EmplaceRecord() @@ -293,8 +339,9 @@ class BelongsTo } } - if (!_loaded) - throw SqlRequireLoadedError(Reflection::TypeNameOf>); + if constexpr (IsMandatory) + if (!_loaded) + throw SqlRequireLoadedError(Reflection::TypeNameOf>); } ValueType _referencedFieldValue {}; diff --git a/src/examples/test_chinook/main.cpp b/src/examples/test_chinook/main.cpp index aa2f4ff7..3ac679da 100644 --- a/src/examples/test_chinook/main.cpp +++ b/src/examples/test_chinook/main.cpp @@ -144,13 +144,16 @@ int main() for (auto& track: dm.Query().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) } } diff --git a/src/tests/DataMapper/CreateTests.cpp b/src/tests/DataMapper/CreateTests.cpp index 625f954a..13d1c1d5 100644 --- a/src/tests/DataMapper/CreateTests.cpp +++ b/src/tests/DataMapper/CreateTests.cpp @@ -3,6 +3,7 @@ #include "../Utils.hpp" #include "Entities.hpp" +#include "Lightweight/DataMapper/QueryBuilders.hpp" #include @@ -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(nullableFKUserNotSet); REQUIRE(!nullableFKUserNotSet.user.Value().has_value()); + REQUIRE(!nullableFKUserNotSet.user.Record().has_value()); } - // NOLINTEND(bugprone-unchecked-optional-access) diff --git a/src/tests/DataMapper/RelationTests.cpp b/src/tests/DataMapper/RelationTests.cpp index 5aa5f491..ee267175 100644 --- a/src/tests/DataMapper/RelationTests.cpp +++ b/src/tests/DataMapper/RelationTests.cpp @@ -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(); + + 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(nullableFKUserNotSet); + REQUIRE(!nullableFKUserNotSet.user.Value().has_value()); + REQUIRE(!nullableFKUserNotSet.user.Record().transform(Light::Unwrap).value_or(User{}).id.Value()); +} + // NOLINTEND(bugprone-unchecked-optional-access)