Lightweight 0.20260921.0
Loading...
Searching...
No Matches
QueryBuilders.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../SqlConnection.hpp"
6#include "../SqlQueryFormatter.hpp"
7#include "../SqlStatement.hpp"
8#include "../Utils.hpp"
9#include "BelongsTo.hpp"
10#include "Field.hpp"
11#include "Record.hpp"
12
13#include <cstdint>
14#include <span>
15#include <vector>
16
17namespace Lightweight
18{
19
20class DataMapper;
21
22/// Structural type for options for DataMapper queries.
23/// This allows to configure behavior of the queries at compile time
24/// when using query builder directly from the DataMapper
26{
27 /// Whether to automatically load relations when querying records.
28 bool loadRelations { true };
29
30 /// @brief How many levels of relations to eagerly batch-load after a query materializes.
31 ///
32 /// `0` (the default) leaves every relation to load on demand, one query per record touched.
33 /// A value of `N` resolves every `BelongsTo` and `HasMany` reachable within `N` levels for the
34 /// whole result set at once, at a bounded number of queries per relation per level:
35 ///
36 /// @code
37 /// // Tracks, their albums, and those albums' artists - a constant number of queries.
38 /// auto tracks = dm.Query<Track, DataMapperOptions { .eagerLoadDepth = 2 }>().All();
39 /// @endcode
40 ///
41 /// Use `With<>()` instead when only some relations are needed: this option descends into
42 /// *every* relation of every record it reaches, which is more queries and more rows than a
43 /// named path, and instantiates the loader for the whole reachable relation graph. The depth is
44 /// what bounds that: a cyclic graph (a self-referencing record, or A -> B -> A) terminates
45 /// because the recursion is cut at a compile-time constant.
46 ///
47 /// `HasOneThrough`, `HasManyThrough` and `CompositeForeignKey` are not batch-loadable and keep
48 /// their on-demand behaviour.
49 size_t eagerLoadDepth { 0 };
50};
51
52/// Selects whether a query builder's finisher methods execute synchronously or asynchronously.
53///
54/// In @c Synchronous mode a finisher (e.g. @c All(), @c First(n)) runs immediately and returns its
55/// plain result. In @c Asynchronous mode the very same finisher offloads its work to the connection's
56/// async backend and returns a @c Async::Task of that result instead, to be @c co_await -ed.
57enum class SqlQueryExecutionMode : std::uint8_t
58{
59 /// The finisher runs on the calling thread and returns its result directly.
60 Synchronous,
61 /// The finisher returns an @c Async::Task that offloads the work and resumes the awaiting coroutine.
62 Asynchronous,
63};
64
65/// Main API for mapping records to C++ from the database using high level C++ syntax.
66///
67/// @ingroup DataMapper
68template <typename Record, typename Derived, DataMapperOptions QueryOptions = {}>
69class [[nodiscard]] SqlCoreDataMapperQueryBuilder: public SqlBasicSelectQueryBuilder<Derived>
70{
71 private:
72 DataMapper& _dm;
73 SqlQueryFormatter const& _formatter;
74
75 std::string _fields;
76 std::vector<SqlVariant> _boundInputs;
77
78 /// One batched relation loader, as requested by `With<&Record::relation>()`.
79 ///
80 /// A plain function pointer rather than a `std::function`: every `With<>()` names its relation at
81 /// compile time, so the loader is a single stateless instantiation and the builder pays one pointer
82 /// per requested relation, with no type-erasure allocation and no change to the builder's type -
83 /// which keeps the fluent chain and the asynchronous execution mode working unchanged.
84 using RelationPreloader = void (*)(DataMapper&, std::span<Record* const>);
85 std::vector<RelationPreloader> _relationPreloaders;
86
87 friend class SqlWhereClauseBuilder<Derived>;
88
89 LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SearchCondition() noexcept
90 {
91 return this->_query.searchCondition;
92 }
93
94 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE SqlQueryFormatter const& Formatter() const noexcept
95 {
96 return _formatter;
97 }
98
99 protected:
100 /// Constructs a query builder with the given data mapper and field list.
101 LIGHTWEIGHT_FORCE_INLINE explicit SqlCoreDataMapperQueryBuilder(DataMapper& dm, std::string fields) noexcept;
102
103 public:
104 // The public finisher methods below are thin dispatchers: each forwards to its synchronous
105 // implementation (the *Impl members) through RunFinisher(), which either calls it directly
106 // (Synchronous mode) or offloads it to the connection's async backend and returns an
107 // Async::Task (Asynchronous mode). The execution mode is carried by the Derived type
108 // (Derived::QueryExecution), so the same fluent builder serves both Query() and QueryAsync().
109
110 /// Executes a SELECT 1 ... query and returns true if a record exists
111 /// We do not provide db specific syntax to check this but reuse the First() implementation
112 [[nodiscard]] auto Exist()
113 {
114 return RunFinisher([this] { return ExistImpl(); });
115 }
116
117 /// Executes a SELECT COUNT query and returns the number of records found.
118 ///
119 /// A preceding @c GroupBy is honored, making the query count per group. Since only a single
120 /// value is returned, the caller receives the count of one arbitrary group: the emitted
121 /// SELECT COUNT query carries no ORDER BY (a preceding @c OrderBy does not apply to it), so
122 /// which group's count is read back is unspecified and may differ between database systems
123 /// and between runs.
124 [[nodiscard]] auto Count()
125 {
126 return RunFinisher([this] { return CountImpl(); });
127 }
128
129 /// Executes a SELECT query and returns all records found.
130 [[nodiscard]] auto All()
131 {
132 return RunFinisher([this] { return AllImpl(); });
133 }
134
135 /// @brief Eagerly loads the given relation for every record the query returns, in a bounded
136 /// number of queries instead of one per record.
137 ///
138 /// Touching a relation on a query result normally loads it on demand, one query per record - the
139 /// N+1 problem. `With<>()` instead resolves the relation for the whole result set after it has
140 /// been materialized: the keys are collected, the related rows are fetched with `WHERE ... IN
141 /// (...)` (chunked, see @ref SqlQueryFormatter::MaxInPredicateValues), and the rows are
142 /// distributed to the records in memory.
143 ///
144 /// Call it once per relation to load; the calls chain.
145 ///
146 /// Naming several relations forms a *path*: the first is resolved for the result set, the records
147 /// it loaded are then gathered and the next relation resolved for all of them at once. A path of
148 /// any length still costs a constant number of queries per level, never one per record - which is
149 /// what a nested `BelongsTo` needs, since each record holds its own copy of the target and
150 /// touching that copy's own relation would otherwise be an N+1 one level down.
151 ///
152 /// @tparam RelationPath One or more relations, in the form of `&Record::FieldName`. The first
153 /// must be a member of the queried record, each subsequent one a member of the preceding
154 /// relation's target. Supported for `BelongsTo` and `HasMany`; naming any other member is
155 /// a compile error.
156 ///
157 /// @code
158 /// auto tracks = dm.Query<Track>()
159 /// .With<&Track::album>() // one extra SELECT ... WHERE id IN (...)
160 /// .With<&Track::album, &Album::artist>() // one more, for every album at once
161 /// .All();
162 ///
163 /// for (auto& track: tracks)
164 /// std::println("{} - {}", track.album.Record().title,
165 /// track.album.Record().artist.Record().name); // no queries here
166 /// @endcode
167 ///
168 /// @note Relations that were not named keep their usual on-demand behaviour. Combining `With<>()`
169 /// with `DataMapperOptions { .loadRelations = false }` therefore makes any *unrequested*
170 /// relation throw `SqlRequireLoadedError` on access rather than quietly issuing a query.
171 /// To eagerly load everything instead of naming paths, see
172 /// @ref DataMapperOptions::eagerLoadDepth.
173 ///
174 /// @return This builder, for chaining.
175 template <auto... RelationPath>
176 requires DataMapperRecord<Record> && (sizeof...(RelationPath) >= 1)
177 [[nodiscard]] Derived& With();
178
179 /// Executes a DELETE query.
180 [[nodiscard]] auto Delete()
181 {
182 return RunFinisher([this] { return DeleteImpl(); });
183 }
184
185 /// @brief Executes a SELECT query and returns all records found for the specified field.
186 ///
187 /// @tparam Field The field to select from the record, in the form of &Record::FieldName.
188 ///
189 /// @returns A vector of values of the type of the specified field.
190 ///
191 /// @code
192 /// auto dm = DataMapper {};
193 /// auto const ages = dm.Query<Person>()
194 /// .OrderBy(FieldNameOf<&Person::age>, SqlResultOrdering::ASCENDING)
195 /// .All<&Person::age>();
196 /// @endcode
197 template <auto Field>
198#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
199 requires(is_aggregate_type(parent_of(Field)))
200#else
201 requires std::is_member_object_pointer_v<decltype(Field)>
202#endif
203 [[nodiscard]] auto All()
204 {
205 return RunFinisher([this] { return this->template AllImpl<Field>(); });
206 }
207
208 /// @brief Executes a SELECT query and returns all records found for the specified field,
209 /// only having the specified fields queried and populated.
210 ///
211 /// @tparam ReferencedFields The fields to select from the record, in the form of &Record::FieldName.
212 ///
213 /// @returns A vector of records with the given fields populated.
214 ///
215 /// @code
216 /// auto dm = DataMapper {};
217 /// auto const ages = dm.Query<Person>()
218 /// .OrderBy(FieldNameOf<&Person::age>, SqlResultOrdering::ASCENDING)
219 /// .All<&Person::name, &Person::age>();
220 /// @endcode
221 template <auto... ReferencedFields>
222 requires(sizeof...(ReferencedFields) >= 2)
223 [[nodiscard]] auto All()
224 {
225 return RunFinisher([this] { return this->template AllImpl<ReferencedFields...>(); });
226 }
227
228 /// Executes a SELECT query for the first record found and returns it.
229 [[nodiscard]] auto First()
230 {
231 return RunFinisher([this] { return FirstImpl(); });
232 }
233
234 /// @brief Executes the query to get a single scalar value from the first record found.
235 ///
236 /// @tparam Field The field to select from the record, in the form of &Record::FieldName.
237 ///
238 /// @returns an optional value of the type of the field, or an empty optional if no record was found.
239 template <auto Field>
240#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
241 requires(is_aggregate_type(parent_of(Field)))
242#else
243 requires std::is_member_object_pointer_v<decltype(Field)>
244#endif
245 [[nodiscard]] auto First()
246 {
247 return RunFinisher([this] { return this->template FirstImpl<Field>(); });
248 }
249
250 /// @brief Executes a SELECT query for the first record found and returns it with only the specified fields populated.
251 ///
252 /// @tparam ReferencedFields The fields to select from the record, in the form of &Record::FieldName.
253 ///
254 /// @returns an optional record with only the specified fields populated, or an empty optional if no record was found.
255 template <auto... ReferencedFields>
256 requires(sizeof...(ReferencedFields) >= 2)
257 [[nodiscard]] auto First()
258 {
259 return RunFinisher([this] { return this->template FirstImpl<ReferencedFields...>(); });
260 }
261
262 /// Executes a SELECT query for the first n records found and returns them.
263 [[nodiscard]] auto First(size_t n)
264 {
265 return RunFinisher([this, n] { return FirstImpl(n); });
266 }
267
268 /// Executes a SELECT query for the first n records with only the specified fields populated.
269 template <auto... ReferencedFields>
270 [[nodiscard]] auto First(size_t n)
271 {
272 return RunFinisher([this, n] { return this->template FirstImpl<ReferencedFields...>(n); });
273 }
274
275 /// Executes a SELECT query for a range of records and returns them.
276 [[nodiscard]] auto Range(size_t offset, size_t limit)
277 {
278 return RunFinisher([this, offset, limit] { return RangeImpl(offset, limit); });
279 }
280
281 /// Executes a SELECT query for a range of records with only the specified fields populated.
282 template <auto... ReferencedFields>
283 [[nodiscard]] auto Range(size_t offset, size_t limit)
284 {
285 return RunFinisher([this, offset, limit] { return this->template RangeImpl<ReferencedFields...>(offset, limit); });
286 }
287
288 private:
289 /// Dispatches a finisher according to the builder's execution mode.
290 ///
291 /// In @c Synchronous mode the finisher is invoked directly and its result returned. In
292 /// @c Asynchronous mode it is offloaded to the connection's async backend and an
293 /// @c Async::Task wrapping its result is returned instead.
294 ///
295 /// @param finisher A nullary callable running one of the synchronous @c *Impl methods.
296 /// @return The finisher's result (Synchronous) or an @c Async::Task of it (Asynchronous).
297 ///
298 /// @note Defined out-of-line in DataMapper.hpp, where @c DataMapper is a complete type (the
299 /// async branch dereferences it via @c _dm.Connection().AsyncBackend()).
300 template <typename Finisher>
301 auto RunFinisher(Finisher finisher);
302
303 // Synchronous implementations shared by both execution modes. The public finishers above
304 // forward to these; the SQL building and result mapping live here exactly once.
305
306 /// Runs every loader registered through `With<>()` over the materialized result set.
307 ///
308 /// @param records The records to resolve the requested relations for.
309 void RunRelationPreloaders(std::span<Record> records);
310
311 [[nodiscard]] bool ExistImpl();
312 [[nodiscard]] size_t CountImpl();
313 [[nodiscard]] std::vector<Record> AllImpl();
314 void DeleteImpl();
315
316 template <auto Field>
317#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
318 requires(is_aggregate_type(parent_of(Field)))
319#else
320 requires std::is_member_object_pointer_v<decltype(Field)>
321#endif
322 [[nodiscard]] auto AllImpl() -> std::vector<ReferencedFieldTypeOf<Field>>;
323
324 template <auto... ReferencedFields>
325 requires(sizeof...(ReferencedFields) >= 2)
326 [[nodiscard]] auto AllImpl() -> std::vector<Record>;
327
328 [[nodiscard]] std::optional<Record> FirstImpl();
329
330 template <auto Field>
331#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
332 requires(is_aggregate_type(parent_of(Field)))
333#else
334 requires std::is_member_object_pointer_v<decltype(Field)>
335#endif
336 [[nodiscard]] auto FirstImpl() -> std::optional<ReferencedFieldTypeOf<Field>>;
337
338 template <auto... ReferencedFields>
339 requires(sizeof...(ReferencedFields) >= 2)
340 [[nodiscard]] auto FirstImpl() -> std::optional<Record>;
341
342 [[nodiscard]] std::vector<Record> FirstImpl(size_t n);
343
344 template <auto... ReferencedFields>
345 [[nodiscard]] std::vector<Record> FirstImpl(size_t n);
346
347 [[nodiscard]] std::vector<Record> RangeImpl(size_t offset, size_t limit);
348
349 template <auto... ReferencedFields>
350 [[nodiscard]] std::vector<Record> RangeImpl(size_t offset, size_t limit);
351};
352
353/// @brief Represents a query builder that retrieves all fields of a record.
354///
355/// @ingroup DataMapper
356///
357/// @tparam Execution Whether the finisher methods execute synchronously or asynchronously.
358/// @c DataMapper::Query selects @c Synchronous, @c DataMapper::QueryAsync selects
359/// @c Asynchronous; the rest of the fluent builder is identical for both.
360template <typename Record,
361 DataMapperOptions QueryOptions,
362 SqlQueryExecutionMode Execution = SqlQueryExecutionMode::Synchronous>
363class [[nodiscard]] SqlAllFieldsQueryBuilder final:
364 public SqlCoreDataMapperQueryBuilder<Record, SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>, QueryOptions>
365{
366 private:
367 friend class DataMapper;
368 friend class SqlCoreDataMapperQueryBuilder<Record,
369 SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>,
370 QueryOptions>;
371
372 /// The execution mode (synchronous/asynchronous) read by the CRTP base to dispatch finishers.
373 static constexpr SqlQueryExecutionMode QueryExecution = Execution;
374
375 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(DataMapper& dm, std::string fields) noexcept:
377 dm, std::move(fields)
378 }
379 {
380 }
381
382 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<Record>* records);
383 static void ReadResult(SqlServerType sqlServerType, SqlResultCursor reader, std::optional<Record>* optionalRecord);
384};
385
386/// @brief Specialization of SqlAllFieldsQueryBuilder for the case when we return std::tuple
387/// of two records
388///
389/// @ingroup DataMapper
390/// @todo deprecate this in favor of a more generic tuple support
391template <typename FirstRecord, typename SecondRecord, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
392class [[nodiscard]] SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution> final:
394 std::tuple<FirstRecord, SecondRecord>,
395 SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution>,
396 QueryOptions>
397{
398 private:
399 using RecordType = std::tuple<FirstRecord, SecondRecord>;
400 friend class DataMapper;
401 friend class SqlCoreDataMapperQueryBuilder<RecordType,
402 SqlAllFieldsQueryBuilder<RecordType, QueryOptions, Execution>,
403 QueryOptions>;
404
405 /// The execution mode (synchronous/asynchronous) read by the CRTP base to dispatch finishers.
406 static constexpr SqlQueryExecutionMode QueryExecution = Execution;
407
408 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(DataMapper& dm, std::string fields) noexcept:
411 QueryOptions> { dm, std::move(fields) }
412 {
413 }
414
415 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records);
416};
417
418} // namespace Lightweight
Main API for mapping records to and from the database using high level C++ syntax.
Represents a query builder that retrieves all fields of a record.
auto All()
Executes a SELECT query and returns all records found for the specified field, only having the specif...
auto All()
Executes a SELECT query and returns all records found for the specified field.
auto Delete()
Executes a DELETE query.
auto Range(size_t offset, size_t limit)
Executes a SELECT query for a range of records and returns them.
auto Range(size_t offset, size_t limit)
Executes a SELECT query for a range of records with only the specified fields populated.
auto First()
Executes the query to get a single scalar value from the first record found.
auto First(size_t n)
Executes a SELECT query for the first n records with only the specified fields populated.
auto First()
Executes a SELECT query for the first record found and returns it.
auto All()
Executes a SELECT query and returns all records found.
auto First(size_t n)
Executes a SELECT query for the first n records found and returns them.
auto First()
Executes a SELECT query for the first record found and returns it with only the specified fields popu...
API to format SQL queries for different SQL dialects.
Represents a record type that can be used with the DataMapper.
Definition Record.hpp:52
size_t eagerLoadDepth
How many levels of relations to eagerly batch-load after a query materializes.
bool loadRelations
Whether to automatically load relations when querying records.
Represents a single column in a table.
Definition Field.hpp:84