5#include "../SqlStatement.hpp"
30 struct MemberPointeeType;
32 template <
typename Member,
typename Owner>
33 struct MemberPointeeType<Member Owner::*>
56template <auto FromPtr, auto IntoPtr>
60 static constexpr auto From = FromPtr;
63 static constexpr auto Into = IntoPtr;
65#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
79#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
81 using FromField = std::remove_cvref_t<
typename[:std::meta::type_of(FromPtr):]>;
84 using IntoField = std::remove_cvref_t<
typename[:std::meta::type_of(IntoPtr):]>;
90 using FromField = std::remove_cvref_t<
typename detail::MemberPointeeType<
decltype(FromPtr)>::type>;
93 using IntoField = std::remove_cvref_t<
typename detail::MemberPointeeType<
decltype(IntoPtr)>::type>;
103 template <
typename RecordT = FromRecord>
104 [[nodiscard]]
static auto const&
FieldOf(RecordT
const& record)
noexcept
106#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
107 return record.[:FromPtr:];
109 return record.*FromPtr;
119 return MemberIndexOf<FromPtr>;
133 template <
typename T>
134 struct IsConnectionType: std::false_type
138 template <auto FromPtr, auto IntoPtr>
139 struct IsConnectionType<Connection<FromPtr, IntoPtr>>: std::true_type
188 static_assert(
sizeof...(Connections) > 0,
"A composite foreign key must connect at least one column.");
192 static constexpr std::size_t
Count =
sizeof...(Connections);
195 using Child = std::tuple_element_t<0, std::tuple<
typename Connections::FromRecord...>>;
198 using ReferencedRecord = std::tuple_element_t<0, std::tuple<
typename Connections::IntoRecord...>>;
200 static_assert((std::same_as<typename Connections::FromRecord, Child> && ...),
201 "Every Connection of a composite foreign key must start at the same record. "
202 "Check that each Connection's first pointer-to-member names this record.");
204 static_assert((std::same_as<typename Connections::IntoRecord, ReferencedRecord> && ...),
205 "Every Connection of a composite foreign key must point at the same record. "
206 "A foreign key references one table; splitting it across two is not expressible.");
212 static_assert((std::same_as<typename Connections::FromField::ValueType, typename Connections::IntoField::ValueType>
214 "Each connected column pair must hold the same value type. A mismatch here usually "
215 "means two connections were transposed.");
217 static_assert((Connections::IntoField::IsPrimaryKey && ...),
218 "A composite foreign key must reference primary key columns. "
219 "Check the PrimaryKey marker on the referenced record's members.");
231 auto const indices = std::array { Connections::IntoMemberIndex... };
232 for (
auto const outer: std::views::iota(std::size_t { 0 }, indices.size()))
233 for (
auto const inner: std::views::iota(outer + 1, indices.size()))
234 if (indices[outer] == indices[inner])
238 "Two Connections of a composite foreign key reference the same member of the referenced "
239 "record. Each column of the key must be connected exactly once.");
249 using ValueType = std::tuple<
typename Connections::FromField::ValueType...>;
253 static constexpr auto IntoIndices = std::array { Connections::IntoMemberIndex... };
259 template <std::
size_t Slot>
260 static constexpr std::size_t ConnectionForSlot = []()
consteval {
261 for (
auto const candidate: std::views::iota(std::size_t { 0 },
Count))
263 auto rank = std::size_t { 0 };
264 for (
auto const other: IntoIndices)
265 if (other < IntoIndices[candidate])
274 template <std::
size_t Slot>
275 using ConnectionAtSlot = std::tuple_element_t<ConnectionForSlot<Slot>, std::tuple<Connections...>>;
278 template <std::
size_t Slot>
279 [[nodiscard]]
static decltype(
auto) ValueAtSlot(
Child const& record)
281 return ConnectionAtSlot<Slot>::FieldOf(record).Value();
290 return std::tuple<typename ConnectionAtSlot<Slot>::FromField::ValueType...> {};
291 }(std::index_sequence_for<Connections...> {}));
303 return ValueType { Connections::FieldOf(record).Value()... };
314 static_assert(
Count == RecordPrimaryKeyCount<ReferencedRecord>,
315 "A composite foreign key must connect every primary key column of the referenced "
316 "record. Connecting only some of them cannot identify a row.");
321 static_assert(((!std::same_as<typename Connections::FromRecord, typename Connections::IntoRecord>
322 || Connections::FromMemberIndex() != Connections::IntoMemberIndex)
324 "A Connection must join two different members. Pairing a member with itself reads "
325 "a value out of a record only to look the same record up by it.");
346 return [&]<std::size_t... Slot>(std::index_sequence<Slot...>) {
348 }(std::index_sequence_for<Connections...> {});
362 return Load().transform([
this] {
return std::reference_wrapper<ReferencedRecord const> { *_record }; });
366 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
constexpr bool IsLoaded() const noexcept
368 return _record.get() !=
nullptr;
372 LIGHTWEIGHT_FORCE_INLINE
void Unload() noexcept
381 LIGHTWEIGHT_FORCE_INLINE
constexpr void EmplaceRecord(std::shared_ptr<ReferencedRecord> record)
noexcept
383 _record = std::move(record);
391 if (
auto const loaded = Load(); !loaded)
392 throw SqlRequireLoadedError(Reflection::TypeNameOf<std::remove_cvref_t<
decltype(*
this)>>, loaded.error());
393 return _record.get();
400 std::function<RelationResult<std::shared_ptr<ReferencedRecord>>()>
loadReference {};
405 return std::weak_ordering::equivalent;
425 _loader = std::move(loader);
448 return std::unexpected { *_loadError };
456 _loadError = loaded.error();
457 return std::unexpected { loaded.error() };
459 _record = std::move(*loaded);
464 mutable std::shared_ptr<ReferencedRecord> _record {};
465 mutable std::optional<RelationError> _loadError {};
470 template <
typename T>
471 struct IsCompositeForeignKeyType: std::false_type
475 template <ConnectionType... Connections>
476 struct IsCompositeForeignKeyType<CompositeForeignKey<Connections...>>: std::true_type
Represents a foreign key spanning several columns.
LIGHTWEIGHT_FORCE_INLINE RelationResult< std::reference_wrapper< ReferencedRecord const > > Record() const
Retrieves the referenced record, loading it on first access.
LIGHTWEIGHT_FORCE_INLINE void Unload() noexcept
Discards the loaded record, so the next access loads it again.
LIGHTWEIGHT_FORCE_INLINE constexpr bool IsLoaded() const noexcept
LIGHTWEIGHT_FORCE_INLINE ReferencedRecord const * operator->() const
static constexpr std::size_t Count
Number of columns this foreign key spans.
std::weak_ordering operator<=>(CompositeForeignKey const &other) const noexcept=default
static ValueType ValuesOf(Child const &record)
std::tuple_element_t< 0, std::tuple< typename Connections::FromRecord... > > Child
The record holding the foreign key, i.e. the one declaring this member.
static OrderedValueType OrderedValuesOf(Child const &record)
LIGHTWEIGHT_FORCE_INLINE constexpr void EmplaceRecord(std::shared_ptr< ReferencedRecord > record) noexcept
std::tuple< typename Connections::FromField::ValueType... > ValueType
The tuple of foreign key values, in the order the connections are declared.
decltype([]< std::size_t... Slot >(std::index_sequence< Slot... >) { return std::tuple< typename ConnectionAtSlot< Slot >::FromField::ValueType... > {} OrderedValueType
std::tuple_element_t< 0, std::tuple< typename Connections::IntoRecord... > > ReferencedRecord
The referenced record.
static constexpr void AssertCoversReferencedKey() noexcept
void SetAutoLoader(Loader loader)
bool operator==(CompositeForeignKey const &other) const noexcept=default
Equality comparison operator.
Raised by the relation shortcuts that cannot return a RelationResult - operator->,...
Satisfied by Connection specializations.
std::expected< T, RelationError > RelationResult
The outcome of a relation access: the requested value, or why it is unavailable.
constexpr bool IsCompositeForeignKey
Whether T is a CompositeForeignKey.
Carries the deferred load, installed by the DataMapper.
bool operator==(Loader const &) const noexcept
std::weak_ordering operator<=>(Loader const &) const noexcept
Loaders carry no comparable state of their own, so any two are considered equivalent.
std::function< RelationResult< std::shared_ptr< ReferencedRecord > >()> loadReference
Loads the referenced record: RelationError::NotFound when none exists.
One column pair of a composite foreign key: "this record's column references that one".
static constexpr std::size_t IntoMemberIndex
static consteval std::size_t FromMemberIndex() noexcept
MemberClassType< decltype(IntoPtr)> IntoRecord
The record this connection points at.
static auto const & FieldOf(RecordT const &record) noexcept
std::remove_cvref_t< typename detail::MemberPointeeType< decltype(FromPtr)>::type > FromField
MemberClassType< decltype(FromPtr)> FromRecord
The record this connection starts from.
static constexpr auto Into
Pointer to the referenced record's primary key member.
static constexpr auto From
Pointer to this record's foreign key member.
std::remove_cvref_t< typename detail::MemberPointeeType< decltype(IntoPtr)>::type > IntoField
The field type on the referenced record's side.