ORM C++
Loading...
Searching...
No Matches
projection_query.hpp
1#pragma once
2
3#include <algorithm>
4#include <cstddef>
5#include <optional>
6#include <stdexcept>
7#include <string>
8#include <type_traits>
9#include <unordered_set>
10#include <utility>
11#include <vector>
12
13#include "orm-cxx/model/ColumnType.hpp"
14#include "orm-cxx/model/Mapping.hpp"
15#include "orm-cxx/query/SelectSpec.hpp"
16#include "orm-cxx/reflection/Reflection.hpp"
17
18namespace orm
19{
20class DatabaseCore;
21template <typename SchemaType>
22class Database;
23
24namespace detail
25{
26inline auto isSupportedProjectionResultType(model::ColumnType type) -> bool
27{
28 switch (type)
29 {
30 case model::ColumnType::Bool:
31 case model::ColumnType::Char:
32 case model::ColumnType::UnsignedChar:
33 case model::ColumnType::Short:
34 case model::ColumnType::UnsignedShort:
35 case model::ColumnType::Int:
36 case model::ColumnType::UnsignedInt:
37 case model::ColumnType::LongLong:
38 case model::ColumnType::UnsignedLongLong:
39 case model::ColumnType::Float:
40 case model::ColumnType::Double:
41 case model::ColumnType::String:
42 return true;
43 case model::ColumnType::Uuid:
44 return false;
45 }
46
47 return false;
48}
49
51{
52 std::string name;
53 std::optional<model::ColumnType> type;
54};
55
56template <typename T>
57consteval auto projectionLogicalType() -> std::optional<model::ColumnType>
58{
59 using field_t = std::remove_cvref_t<T>;
60 using value_t = std::remove_cv_t<model::detail::static_optional_value_t<field_t>>;
61 if constexpr (requires { model::LogicalTypeTraits<value_t>::value; })
62 {
63 return model::LogicalTypeTraits<value_t>::value;
64 }
65 else
66 {
67 return std::nullopt;
68 }
69}
70
71inline auto validateProjectionAliasNames(const std::vector<query::Projection>& projections,
72 const std::vector<ProjectionResultField>& fields) -> void
73{
74 if (projections.empty())
75 {
76 throw std::invalid_argument{"Projection query requires at least one projected field"};
77 }
78
79 std::unordered_set<std::string> resultFields;
80
81 for (const auto& field : fields)
82 {
83 if (not field.type.has_value() or not isSupportedProjectionResultType(field.type.value()))
84 {
85 throw std::invalid_argument{"Unsupported projection result field type: " + field.name};
86 }
87
88 resultFields.insert(field.name);
89 }
90
91 std::unordered_set<std::string> projectedFields;
92
93 for (const auto& projection : projections)
94 {
95 if (projection.resultField.empty())
96 {
97 throw std::invalid_argument{"Projection alias must not be empty"};
98 }
99
100 if (not resultFields.contains(projection.resultField))
101 {
102 throw std::invalid_argument{"Projection alias does not match a result field: " + projection.resultField};
103 }
104
105 if (not projectedFields.insert(projection.resultField).second)
106 {
107 throw std::invalid_argument{"Duplicate projection alias: " + projection.resultField};
108 }
109 }
110
111 for (const auto& resultField : resultFields)
112 {
113 if (not projectedFields.contains(resultField))
114 {
115 throw std::invalid_argument{"Missing projection alias for result field: " + resultField};
116 }
117 }
118}
119
120template <typename Result>
121auto validateProjectionAliases(const std::vector<query::Projection>& projections) -> void
122{
123 std::vector<ProjectionResultField> resultFields;
124 resultFields.reserve(reflection::fieldCount<Result>);
125
126 [&]<std::size_t... Indices>(std::index_sequence<Indices...>)
127 {
128 constexpr auto fields = reflection::fields<Result>();
129 auto appendField = [&]<std::size_t Index>()
130 {
131 constexpr auto type = projectionLogicalType<reflection::field_type_t<Result, Index>>();
132 resultFields.emplace_back(std::string{fields[Index].name}, type);
133 };
134 (appendField.template operator()<Indices>(), ...);
135 }(std::make_index_sequence<reflection::fieldCount<Result>>{});
136
137 validateProjectionAliasNames(projections, resultFields);
138}
139} // namespace detail
140
147template <typename Source, typename Result>
148class ProjectionQuery
149{
150public:
151 ProjectionQuery() = default;
152
153 template <typename... Projections>
154 auto project(Projections... projections) -> ProjectionQuery<Source, Result>&
155 {
156 data.projections = {std::move(projections)...};
157 detail::validateProjectionAliases<Result>(data.projections);
158
159 return *this;
160 }
161
162 auto where(const query::Predicate& predicate) -> ProjectionQuery<Source, Result>&
163 {
164 data.predicate = predicate;
165
166 return *this;
167 }
168
169 auto andWhere(const query::Predicate& predicate) -> ProjectionQuery<Source, Result>&
170 {
171 data.predicate = data.predicate.has_value() ? data.predicate.value() && predicate : predicate;
172
173 return *this;
174 }
175
176 auto orWhere(const query::Predicate& predicate) -> ProjectionQuery<Source, Result>&
177 {
178 data.predicate = data.predicate.has_value() ? data.predicate.value() || predicate : predicate;
179
180 return *this;
181 }
182
183 template <typename... Orders>
184 auto orderBy(Orders... orders) -> ProjectionQuery<Source, Result>&
185 {
186 data.orderBy = {std::move(orders)...};
187
188 return *this;
189 }
190
191 template <typename... Columns>
192 auto groupBy(Columns... columns) -> ProjectionQuery<Source, Result>&
193 {
194 data.groupBy = {std::move(columns)...};
195
196 return *this;
197 }
198
199 auto having(const query::AggregatePredicate& predicate) -> ProjectionQuery<Source, Result>&
200 {
201 data.having = predicate;
202
203 return *this;
204 }
205
206 auto andHaving(const query::AggregatePredicate& predicate) -> ProjectionQuery<Source, Result>&
207 {
208 data.having = data.having.has_value() ? data.having.value() && predicate : predicate;
209
210 return *this;
211 }
212
213 auto orHaving(const query::AggregatePredicate& predicate) -> ProjectionQuery<Source, Result>&
214 {
215 data.having = data.having.has_value() ? data.having.value() || predicate : predicate;
216
217 return *this;
218 }
219
220 inline auto distinct() -> ProjectionQuery<Source, Result>&
221 {
222 data.isDistinct = true;
223
224 return *this;
225 }
226
227 inline auto offset(std::size_t offset) -> ProjectionQuery<Source, Result>&
228 {
229 data.offset = offset;
230
231 return *this;
232 }
233
234 inline auto limit(std::size_t limit) -> ProjectionQuery<Source, Result>&
235 {
236 data.limit = limit;
237
238 return *this;
239 }
240
241 inline auto disableJoining() -> ProjectionQuery<Source, Result>&
242 {
243 data.shouldJoin = false;
244
245 return *this;
246 }
247
248private:
249 template <typename>
250 friend class orm::Database;
251 friend class orm::DatabaseCore;
252
253 [[nodiscard]] inline auto getData() const -> const query::SelectSpec&
254 {
255 detail::validateProjectionAliases<Result>(data.projections);
256
257 return data;
258 }
259
260 query::SelectSpec data;
261};
262} // 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 projection_query.hpp:51