Lightweight 0.20260921.0
Loading...
Searching...
No Matches
HasOneThrough.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../SqlStatement.hpp"
6#include "../Utils.hpp"
7#include "Error.hpp"
8#include "Record.hpp"
9
10#include <compare>
11#include <memory>
12#include <type_traits>
13
14namespace Lightweight
15{
16
17/// @brief Represents a one-to-one relationship through a join table.
18///
19/// The `OtherTable` parameter is the record reached through the join table.
20/// The `ThroughSpec` parameter is the join table, which references the current record. It is named
21/// with the @ref Through marker, so that the reader can tell it apart from the referenced record:
22///
23/// @code
24/// struct Account;
25/// struct AccountHistory;
26/// struct Supplier
27/// {
28/// Field<int, PrimaryKey::AutoAssign> id;
29/// HasOneThrough<AccountHistory, Through<Account>> accountHistory;
30/// };
31/// @endcode
32///
33/// Both foreign keys are located by matching the relationship *type*. When either record holds more
34/// than one foreign key into the same table, name the column to single one out - see
35/// the RelationSelector concept and the example on @ref HasManyThrough.
36///
37/// @tparam OtherTable The record type reached through the join table.
38/// @tparam ThroughSpec The join record, wrapped as `Through<T>`. Naming the record bare is deprecated.
39/// @tparam TheOwnerSelector Singles out the join record's foreign key pointing at the *owning* record.
40/// @tparam TheThroughSelector Singles out @p OtherTable's foreign key pointing at the join record.
41///
42/// @see DataMapper, Field, HasManyThrough, Through, RelationSelector
43/// @ingroup DataMapper
44template <typename OtherTable,
45 typename ThroughSpec,
46 auto TheOwnerSelector = AutoDetectRelation,
47 auto TheThroughSelector = AutoDetectRelation>
49{
51 "The selector template arguments of HasOneThrough must be foreign key column names "
52 "(a SqlRealName) or std::nullopt to resolve the relationship automatically.");
53
54 static_assert(!IsThrough<OtherTable>,
55 "The referenced record of HasOneThrough must not be wrapped in Through<>, "
56 "only the join record is.");
57
58 public:
59 /// The record type of the "through" side of the relationship.
61
62 /// The record type of the "Other" side of the relationship.
63 using ReferencedRecord = OtherTable;
64
65 /// Singles out the join record's foreign key pointing at the record owning this relationship.
66 static constexpr auto OwnerSelector = TheOwnerSelector;
67
68 /// Singles out @ref ReferencedRecord's foreign key pointing at @ref ThroughRecord.
69 static constexpr auto ThroughSelector = TheThroughSelector;
70
71 // clang-format off
72
73 /// Emplaces the given record into this relationship.
74 LIGHTWEIGHT_FORCE_INLINE constexpr void EmplaceRecord(std::shared_ptr<ReferencedRecord> record) { _record = std::move(record); }
75
76 /// Retrieves the record in this relationship.
77 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& Record() noexcept { RequireLoaded(); return *_record.get(); }
78
79 /// Retrieves the record in this relationship.
80 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& Record() const noexcept { RequireLoaded(); return *_record.get(); }
81
82 /// Checks if the record is loaded.
83 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr bool IsLoaded() const noexcept { return _record.get() != nullptr; }
84
85 /// Unloads the record from memory.
86 LIGHTWEIGHT_FORCE_INLINE void Unload() noexcept { _record.reset(); }
87
88 /// @brief Retrieves the record in this relationship.
89 /// @note On-demand loads the record if it is not already loaded.
90 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord& operator*() noexcept { RequireLoaded(); return *_record; }
91
92 /// @brief Retrieves the record in this relationship.
93 /// @note On-demand loads the record if it is not already loaded.
94 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const& operator*() const noexcept { RequireLoaded(); return *_record; }
95
96 /// @brief Retrieves the record in this relationship.
97 /// @note On-demand loads the record if it is not already loaded.
98 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord* operator->() noexcept { RequireLoaded(); return _record.get(); }
99
100 /// @brief Retrieves the record in this relationship.
101 /// @note On-demand loads the record if it is not already loaded.
102 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const* operator->() const noexcept { RequireLoaded(); return _record.get(); }
103 // clang-format on
104
105 /// Default three-way comparison operator.
106 std::weak_ordering operator<=>(HasOneThrough const& other) const noexcept = default;
107
108 struct Loader
109 {
110 std::function<std::shared_ptr<ReferencedRecord>()> loadReference {};
111 };
112
113 /// Used internally to configure on-demand loading of the record.
114 void SetAutoLoader(Loader loader)
115 {
116 _loader = std::move(loader);
117 }
118
119 private:
120 void RequireLoaded() const
121 {
122 if (IsLoaded())
123 return;
124
125 if (_loader.loadReference)
126 _record = _loader.loadReference();
127
128 if (!IsLoaded())
129 throw SqlRequireLoadedError { Reflection::TypeNameOf<std::remove_cvref_t<decltype(*this)>> };
130 }
131
132 Loader _loader {};
133
134 // We use shared_ptr to not require ReferencedRecord to be declared before HasOneThrough.
135 mutable std::shared_ptr<ReferencedRecord> _record {};
136};
137
138namespace detail
139{
140 template <typename T>
141 struct IsHasOneThrough: std::false_type
142 {
143 };
144
145 template <typename OtherTable, typename ThroughSpec, auto OwnerSelector, auto ThroughSelector>
146 struct IsHasOneThrough<HasOneThrough<OtherTable, ThroughSpec, OwnerSelector, ThroughSelector>>: std::true_type
147 {
148 };
149} // namespace detail
150
151template <typename T>
152constexpr bool IsHasOneThrough = detail::IsHasOneThrough<std::remove_cvref_t<T>>::value;
153
154} // namespace Lightweight
Represents a one-to-one relationship through a join table.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const & Record() const noexcept
Retrieves the record in this relationship.
LIGHTWEIGHT_FORCE_INLINE void Unload() noexcept
Unloads the record from memory.
void SetAutoLoader(Loader loader)
Used internally to configure on-demand loading of the record.
LIGHTWEIGHT_FORCE_INLINE constexpr bool IsLoaded() const noexcept
Checks if the record is loaded.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const * operator->() const noexcept
Retrieves the record in this relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr void EmplaceRecord(std::shared_ptr< ReferencedRecord > record)
Emplaces the given record into this relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord * operator->() noexcept
Retrieves the record in this relationship.
static constexpr auto OwnerSelector
Singles out the join record's foreign key pointing at the record owning this relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const & operator*() const noexcept
Retrieves the record in this relationship.
static constexpr auto ThroughSelector
Singles out ReferencedRecord's foreign key pointing at ThroughRecord.
std::weak_ordering operator<=>(HasOneThrough const &other) const noexcept=default
Default three-way comparison operator.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & Record() noexcept
Retrieves the record in this relationship.
ThroughRecordOf< ThroughSpec > ThroughRecord
The record type of the "through" side of the relationship.
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord & operator*() noexcept
Retrieves the record in this relationship.
OtherTable ReferencedRecord
The record type of the "Other" side of the relationship.
Represents an error when a record is required to be loaded but is not.
Definition Error.hpp:16
Constrains what may be used to single out one of several foreign keys into the same table.
Definition Record.hpp:134
typename detail::ThroughRecordOfHelper< ThroughSpec >::type ThroughRecordOf
Resolves the join record of a through-relationship from its template argument.
Definition Record.hpp:231
constexpr std::nullopt_t AutoDetectRelation
Selector value meaning "resolve the relationship automatically; the match must be unique".
Definition Record.hpp:119