5#include "../SqlStatement.hpp"
29 struct MemberPointeeType;
31 template <
typename Member,
typename Owner>
32 struct MemberPointeeType<Member Owner::*>
55template <auto FromPtr, auto IntoPtr>
59 static constexpr auto From = FromPtr;
62 static constexpr auto Into = IntoPtr;
64#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
78#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
80 using FromField = std::remove_cvref_t<
typename[:std::meta::type_of(FromPtr):]>;
83 using IntoField = std::remove_cvref_t<
typename[:std::meta::type_of(IntoPtr):]>;
89 using FromField = std::remove_cvref_t<
typename detail::MemberPointeeType<
decltype(FromPtr)>::type>;
92 using IntoField = std::remove_cvref_t<
typename detail::MemberPointeeType<
decltype(IntoPtr)>::type>;
102 template <
typename RecordT = FromRecord>
103 [[nodiscard]]
static auto const&
FieldOf(RecordT
const& record)
noexcept
105#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
106 return record.[:FromPtr:];
108 return record.*FromPtr;
118 return MemberIndexOf<FromPtr>;
132 template <
typename T>
133 struct IsConnectionType: std::false_type
137 template <auto FromPtr, auto IntoPtr>
138 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...> {});
359 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE
constexpr bool IsLoaded() const noexcept
361 return _record.get() !=
nullptr;
365 LIGHTWEIGHT_FORCE_INLINE
void Unload() noexcept
373 LIGHTWEIGHT_FORCE_INLINE
constexpr void EmplaceRecord(std::shared_ptr<ReferencedRecord> record)
noexcept
375 _record = std::move(record);
382 return _record.get();
394 return std::weak_ordering::equivalent;
414 _loader = std::move(loader);
428 void RequireLoaded()
const
441 mutable std::shared_ptr<ReferencedRecord> _record {};
446 template <
typename T>
447 struct IsCompositeForeignKeyType: std::false_type
451 template <ConnectionType... Connections>
452 struct IsCompositeForeignKeyType<CompositeForeignKey<Connections...>>: std::true_type
Represents a foreign key spanning several columns.
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 & Record() 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
LIGHTWEIGHT_FORCE_INLINE constexpr ReferencedRecord const * operator->() const
void SetAutoLoader(Loader loader)
bool operator==(CompositeForeignKey const &other) const noexcept=default
Equality comparison operator.
Represents an error when a record is required to be loaded but is not.
Satisfied by Connection specializations.
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< std::shared_ptr< ReferencedRecord >()> loadReference
Loads and returns the referenced record, or nullptr if 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.