ORM C++
Loading...
Searching...
No Matches
relations.hpp
1#pragma once
2
3#include <array>
4#include <concepts>
5#include <cstddef>
6#include <memory>
7#include <optional>
8#include <string_view>
9#include <tuple>
10#include <type_traits>
11#include <utility>
12#include <vector>
13
14#include "orm-cxx/reflection/Reflection.hpp"
15
16namespace orm
17{
18class DatabaseCore;
19template <typename SchemaType>
20class Database;
21
22namespace detail
23{
24template <typename T>
25class RelationCollection
26{
27public:
28 using value_type = T;
29 using container_type = std::vector<T>;
30 using size_type = typename container_type::size_type;
31 using iterator = typename container_type::iterator;
32 using const_iterator = typename container_type::const_iterator;
33
34 RelationCollection() = default;
35
36 RelationCollection(const RelationCollection& other)
37 : values_(other.values_ == nullptr ? nullptr : std::make_shared<container_type>(*other.values_)),
38 loaded_(other.loaded_)
39 {
40 }
41
42 auto operator=(const RelationCollection& other) -> RelationCollection&
43 {
44 if (this == &other)
45 {
46 return *this;
47 }
48
49 auto copiedValues = other.values_ == nullptr ? std::shared_ptr<container_type>{} :
50 std::make_shared<container_type>(*other.values_);
51 values_ = std::move(copiedValues);
52 loaded_ = other.loaded_;
53 return *this;
54 }
55
56 RelationCollection(RelationCollection&&) noexcept = default;
57 auto operator=(RelationCollection&&) noexcept -> RelationCollection& = default;
58
59 explicit RelationCollection(container_type values)
60 : values_(std::make_shared<container_type>(std::move(values))), loaded_(true)
61 {
62 }
63
64 [[nodiscard]] auto isLoaded() const noexcept -> bool
65 {
66 return loaded_;
67 }
68
69 auto values() -> container_type&
70 {
71 return mutableValues();
72 }
73
74 auto values() const -> const container_type&
75 {
76 return readableValues();
77 }
78
79 auto begin() -> iterator
80 {
81 return values().begin();
82 }
83
84 auto begin() const -> const_iterator
85 {
86 return values().begin();
87 }
88
89 auto cbegin() const -> const_iterator
90 {
91 return values().cbegin();
92 }
93
94 auto end() -> iterator
95 {
96 return values().end();
97 }
98
99 auto end() const -> const_iterator
100 {
101 return values().end();
102 }
103
104 auto cend() const -> const_iterator
105 {
106 return values().cend();
107 }
108
109 [[nodiscard]] auto size() const noexcept -> size_type
110 {
111 return values_ == nullptr ? 0 : values_->size();
112 }
113
114 [[nodiscard]] auto empty() const noexcept -> bool
115 {
116 return values_ == nullptr or values_->empty();
117 }
118
119 auto operator[](size_type index) -> T&
120 {
121 return values()[index];
122 }
123
124 auto operator[](size_type index) const -> const T&
125 {
126 return values()[index];
127 }
128
129private:
130 friend class orm::DatabaseCore;
131 template <typename>
132 friend class orm::Database;
133
134 auto setLoaded(container_type values) -> void
135 {
136 values_ = std::make_shared<container_type>(std::move(values));
137 loaded_ = true;
138 }
139
140 auto mutableValues() -> container_type&
141 {
142 if (values_ == nullptr)
143 {
144 values_ = std::make_shared<container_type>();
145 }
146
147 return *values_;
148 }
149
150 auto readableValues() const -> const container_type&
151 {
152 if (values_ == nullptr)
153 {
154 values_ = std::make_shared<container_type>();
155 }
156
157 return *values_;
158 }
159
160 mutable std::shared_ptr<container_type> values_;
161 bool loaded_{};
162};
163} // namespace detail
164
165template <typename T>
167{
168public:
169 using detail::RelationCollection<T>::RelationCollection;
170};
171
172template <typename T>
174{
175public:
176 using detail::RelationCollection<T>::RelationCollection;
177};
178
179namespace detail
180{
181template <typename T>
183{
184 inline static constexpr bool isCollection = false;
185 inline static constexpr bool isOneToMany = false;
186 using Target = void;
187};
188
189template <typename T>
191{
192 inline static constexpr bool isCollection = true;
193 inline static constexpr bool isOneToMany = true;
194 using Target = T;
195};
196
197template <typename T>
199{
200 inline static constexpr bool isCollection = true;
201 inline static constexpr bool isOneToMany = false;
202 using Target = T;
203};
204
205template <typename T>
206inline constexpr bool isRelationCollection = RelationCollectionTraits<std::remove_cv_t<T>>::isCollection;
207
208template <typename T>
210{
211 inline static constexpr bool value = false;
212};
213
214template <typename T>
215struct OptionalRelationCollectionTraits<std::optional<T>>
216{
217 inline static constexpr bool value = isRelationCollection<T>;
218};
219
220template <typename T>
221inline constexpr bool isOptionalRelationCollection = OptionalRelationCollectionTraits<std::remove_cv_t<T>>::value;
222} // namespace detail
223
224template <typename T>
225inline constexpr bool is_relation_collection_v = detail::isRelationCollection<std::remove_cvref_t<T>>;
226
227template <typename T>
228inline constexpr bool is_optional_relation_collection_v = detail::isOptionalRelationCollection<std::remove_cvref_t<T>>;
229
230template <typename T>
231using relation_target_t = typename detail::RelationCollectionTraits<std::remove_cvref_t<T>>::Target;
232
233template <typename T>
234inline constexpr bool is_one_to_many_v =
235 is_relation_collection_v<T> && detail::RelationCollectionTraits<std::remove_cvref_t<T>>::isOneToMany;
236
237namespace detail
238{
239inline constexpr reflection::FixedString emptyRelationName{""};
240
241template <typename>
243
244template <typename Value, typename Owner>
245struct RelationMemberPointerTraits<Value Owner::*>
246{
247 using OwnerType = Owner;
248 using ValueType = Value;
249};
250
251template <auto Member>
252using relation_member_owner_t = typename RelationMemberPointerTraits<std::remove_cv_t<decltype(Member)>>::OwnerType;
253
254template <auto Member>
255using relation_member_value_t = typename RelationMemberPointerTraits<std::remove_cv_t<decltype(Member)>>::ValueType;
256
257template <reflection::FixedString... Names>
259{
260 inline static constexpr std::size_t size = sizeof...(Names);
261 inline static constexpr std::array<std::string_view, sizeof...(Names)> values{Names.view()...};
262};
263
264template <typename T>
265struct IsRelationDescriptor : std::false_type
266{
267};
268
269template <typename T>
270struct IsOneToManyDescriptor : std::false_type
271{
272};
273
274template <typename T>
275struct IsManyToManyDescriptor : std::false_type
276{
277};
278} // namespace detail
279
284template <auto Member, auto MappedByMember = nullptr, reflection::FixedString MappedByName = detail::emptyRelationName>
285 requires std::is_member_object_pointer_v<decltype(Member)>
287{
288 inline static constexpr auto member = Member;
289 inline static constexpr auto mappedByMember = MappedByMember;
290 inline static constexpr auto mappedByName = MappedByName;
291
292 template <auto TargetMember>
293 requires std::is_member_object_pointer_v<decltype(TargetMember)>
294 [[nodiscard]] consteval auto
296 {
297 using collection_t = std::remove_cvref_t<detail::relation_member_value_t<Member>>;
298 using target_t = relation_target_t<collection_t>;
299 static_assert(std::same_as<detail::relation_member_owner_t<TargetMember>, target_t>,
300 "ORM_RELATION_MAPPED_BY_OWNER: mappedBy member must belong to the relation target");
301 return {};
302 }
303
304 template <reflection::FixedString TargetField>
305 [[nodiscard]] consteval auto mappedBy() const -> OneToManyDescriptor<Member, nullptr, TargetField>
306 {
307 static_assert(not TargetField.view().empty(),
308 "ORM_RELATION_MAPPED_BY_EMPTY: mappedBy field name must not be empty");
309 return {};
310 }
311};
312
313namespace detail
314{
315template <auto Member, auto MappedByMember, reflection::FixedString MappedByName>
316struct IsRelationDescriptor<OneToManyDescriptor<Member, MappedByMember, MappedByName>> : std::true_type
317{
318};
319
320template <auto Member, auto MappedByMember, reflection::FixedString MappedByName>
321struct IsOneToManyDescriptor<OneToManyDescriptor<Member, MappedByMember, MappedByName>> : std::true_type
322{
323};
324} // namespace detail
325
329template <auto Member, auto MappedByMember = nullptr, reflection::FixedString MappedByName = detail::emptyRelationName,
330 reflection::FixedString ThroughTable = detail::emptyRelationName,
331 typename OwnerColumnNames = detail::RelationColumnNames<>,
332 typename TargetColumnNames = detail::RelationColumnNames<>>
333 requires std::is_member_object_pointer_v<decltype(Member)>
335{
336 inline static constexpr auto member = Member;
337 inline static constexpr auto mappedByMember = MappedByMember;
338 inline static constexpr auto mappedByName = MappedByName;
339 inline static constexpr auto throughTable = ThroughTable;
340 using OwnerColumns = OwnerColumnNames;
341 using TargetColumns = TargetColumnNames;
342
343 template <reflection::FixedString TableName>
344 [[nodiscard]] consteval auto through() const
346 {
347 static_assert(not TableName.view().empty(),
348 "ORM_RELATION_JUNCTION_EMPTY: junction table name must not be empty");
349 return {};
350 }
351
352 template <auto TargetMember>
353 requires std::is_member_object_pointer_v<decltype(TargetMember)>
354 [[nodiscard]] consteval auto
355 mappedBy() const -> ManyToManyDescriptor<Member, TargetMember, detail::emptyRelationName, ThroughTable,
356 OwnerColumnNames, TargetColumnNames>
357 {
358 using collection_t = std::remove_cvref_t<detail::relation_member_value_t<Member>>;
359 using target_t = relation_target_t<collection_t>;
360 static_assert(std::same_as<detail::relation_member_owner_t<TargetMember>, target_t>,
361 "ORM_RELATION_MAPPED_BY_OWNER: mappedBy member must belong to the relation target");
362 return {};
363 }
364
365 template <reflection::FixedString TargetField>
366 [[nodiscard]] consteval auto mappedBy() const
368 {
369 static_assert(not TargetField.view().empty(),
370 "ORM_RELATION_MAPPED_BY_EMPTY: mappedBy field name must not be empty");
371 return {};
372 }
373
374 template <reflection::FixedString... Names>
375 [[nodiscard]] consteval auto
376 ownerColumns() const -> ManyToManyDescriptor<Member, MappedByMember, MappedByName, ThroughTable,
377 detail::RelationColumnNames<Names...>, TargetColumnNames>
378 {
379 static_assert(sizeof...(Names) > 0,
380 "ORM_RELATION_OWNER_COLUMNS_EMPTY: owner junction columns must not be empty");
381 static_assert((not Names.view().empty() && ...),
382 "ORM_RELATION_OWNER_COLUMN_EMPTY: junction column name must not be empty");
383 return {};
384 }
385
386 template <reflection::FixedString... Names>
387 [[nodiscard]] consteval auto
388 targetColumns() const -> ManyToManyDescriptor<Member, MappedByMember, MappedByName, ThroughTable, OwnerColumnNames,
390 {
391 static_assert(sizeof...(Names) > 0,
392 "ORM_RELATION_TARGET_COLUMNS_EMPTY: target junction columns must not be empty");
393 static_assert((not Names.view().empty() && ...),
394 "ORM_RELATION_TARGET_COLUMN_EMPTY: junction column name must not be empty");
395 return {};
396 }
397};
398
399namespace detail
400{
401template <auto Member, auto MappedByMember, reflection::FixedString MappedByName, reflection::FixedString ThroughTable,
402 typename OwnerColumnNames, typename TargetColumnNames>
404 ManyToManyDescriptor<Member, MappedByMember, MappedByName, ThroughTable, OwnerColumnNames, TargetColumnNames>>
405 : std::true_type
406{
407};
408
409template <auto Member, auto MappedByMember, reflection::FixedString MappedByName, reflection::FixedString ThroughTable,
410 typename OwnerColumnNames, typename TargetColumnNames>
412 ManyToManyDescriptor<Member, MappedByMember, MappedByName, ThroughTable, OwnerColumnNames, TargetColumnNames>>
413 : std::true_type
414{
415};
416} // namespace detail
417
418template <auto Member>
419 requires std::is_member_object_pointer_v<decltype(Member)>
420[[nodiscard]] consteval auto oneToMany() -> OneToManyDescriptor<Member>
421{
422 using field_t = std::remove_cvref_t<detail::relation_member_value_t<Member>>;
423 static_assert(is_one_to_many_v<field_t>, "ORM_RELATION_KIND: oneToMany<Member>() requires an OneToMany<T> member");
424 return {};
425}
426
427template <auto Member>
428 requires std::is_member_object_pointer_v<decltype(Member)>
429[[nodiscard]] consteval auto manyToMany() -> ManyToManyDescriptor<Member>
430{
431 using field_t = std::remove_cvref_t<detail::relation_member_value_t<Member>>;
432 static_assert(is_relation_collection_v<field_t> && not is_one_to_many_v<field_t>,
433 "ORM_RELATION_KIND: manyToMany<Member>() requires a ManyToMany<T> member");
434 return {};
435}
436
437template <typename... Descriptors>
438[[nodiscard]] consteval auto relations(Descriptors... descriptors)
439{
441 "ORM_MODEL_RELATIONS_DEFINITION: orm::relations accepts only typed relation descriptors");
442 return std::tuple<Descriptors...>{descriptors...};
443}
444} // namespace orm
A class representing a database in the ORM framework.
Definition database.hpp:67
Database facade bound to one closed compile-time model schema.
Definition database.hpp:704
Definition relations.hpp:174
Definition relations.hpp:167
Definition relations.hpp:26
Typed many-to-many mapping and its compile-time builder.
Definition relations.hpp:335
Typed one-to-many mapping. The descriptor stores no runtime strings: every member and fallback name i...
Definition relations.hpp:287
Definition relations.hpp:276
Definition relations.hpp:271
Definition relations.hpp:266
Definition relations.hpp:183
Definition relations.hpp:259
Definition relations.hpp:242