Lightweight 0.20261105.1
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 search condition points at this builder's own _boundInputs, so the implicit copy and move
105 // would leave a new builder binding its values into the source's list (#620). Both re-point it.
106 // Assignment stays unavailable, as the reference members already made it.
107
108 /// Copies the builder; the copy binds further values into its own list.
110
111 /// Moves the builder; the new builder binds further values into its own list.
113
117
118 // The public finisher methods below are thin dispatchers: each forwards to its synchronous
119 // implementation (the *Impl members) through RunFinisher(), which either calls it directly
120 // (Synchronous mode) or offloads it to the connection's async backend and returns an
121 // Async::Task (Asynchronous mode). The execution mode is carried by the Derived type
122 // (Derived::QueryExecution), so the same fluent builder serves both Query() and QueryAsync().
123
124 /// Executes a SELECT 1 ... query and returns true if a record exists
125 /// We do not provide db specific syntax to check this but reuse the First() implementation
126 [[nodiscard]] auto Exist()
127 {
128 return RunFinisher([this] { return ExistImpl(); });
129 }
130
131 /// Executes a SELECT COUNT query and returns the number of records found.
132 ///
133 /// A preceding @c GroupBy is honored, making the query count per group. Since only a single
134 /// value is returned, the caller receives the count of one arbitrary group: the emitted
135 /// SELECT COUNT query carries no ORDER BY (a preceding @c OrderBy does not apply to it), so
136 /// which group's count is read back is unspecified and may differ between database systems
137 /// and between runs.
138 [[nodiscard]] auto Count()
139 {
140 return RunFinisher([this] { return CountImpl(); });
141 }
142
143 /// Executes a SELECT query and returns all records found.
144 [[nodiscard]] auto All()
145 {
146 return RunFinisher([this] { return AllImpl(); });
147 }
148
149 /// @brief Eagerly loads the given relation for every record the query returns, in a bounded
150 /// number of queries instead of one per record.
151 ///
152 /// Touching a relation on a query result normally loads it on demand, one query per record - the
153 /// N+1 problem. `With<>()` instead resolves the relation for the whole result set after it has
154 /// been materialized: the keys are collected, the related rows are fetched with `WHERE ... IN
155 /// (...)` (chunked, see @ref SqlQueryFormatter::MaxInPredicateValues), and the rows are
156 /// distributed to the records in memory.
157 ///
158 /// Call it once per relation to load; the calls chain.
159 ///
160 /// Naming several relations forms a *path*: the first is resolved for the result set, the records
161 /// it loaded are then gathered and the next relation resolved for all of them at once. A path of
162 /// any length still costs a constant number of queries per level, never one per record - which is
163 /// what a nested `BelongsTo` needs, since each record holds its own copy of the target and
164 /// touching that copy's own relation would otherwise be an N+1 one level down.
165 ///
166 /// @tparam RelationPath One or more relations, in the form of `&Record::FieldName`. The first
167 /// must be a member of the queried record, each subsequent one a member of the preceding
168 /// relation's target. Supported for `BelongsTo` and `HasMany`; naming any other member is
169 /// a compile error.
170 ///
171 /// @code
172 /// auto tracks = dm.Query<Track>()
173 /// .With<&Track::album>() // one extra SELECT ... WHERE id IN (...)
174 /// .With<&Track::album, &Album::artist>() // one more, for every album at once
175 /// .All();
176 ///
177 /// for (auto& track: tracks)
178 /// std::println("{} - {}", track.album->title, track.album->artist->name); // no queries here
179 /// @endcode
180 ///
181 /// @note Relations that were not named keep their usual on-demand behaviour. Combining `With<>()`
182 /// with `DataMapperOptions { .loadRelations = false }` therefore makes any *unrequested*
183 /// relation throw `SqlRequireLoadedError` on access rather than quietly issuing a query.
184 /// To eagerly load everything instead of naming paths, see
185 /// @ref DataMapperOptions::eagerLoadDepth.
186 ///
187 /// @return This builder, for chaining.
188 template <auto... RelationPath>
189 requires DataMapperRecord<Record> && (sizeof...(RelationPath) >= 1)
190 [[nodiscard]] Derived& With();
191
192 /// Executes a DELETE query.
193 [[nodiscard]] auto Delete()
194 {
195 return RunFinisher([this] { return DeleteImpl(); });
196 }
197
198 /// @brief Executes a SELECT query and returns all records found for the specified field.
199 ///
200 /// @tparam Field The field to select from the record, in the form of &Record::FieldName.
201 ///
202 /// @returns A vector of values of the type of the specified field.
203 ///
204 /// @code
205 /// auto dm = DataMapper {};
206 /// auto const ages = dm.Query<Person>()
207 /// .OrderBy(FieldNameOf<&Person::age>, SqlResultOrdering::ASCENDING)
208 /// .All<&Person::age>();
209 /// @endcode
210 template <auto Field>
211#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
212 requires(is_aggregate_type(parent_of(Field)))
213#else
214 requires std::is_member_object_pointer_v<decltype(Field)>
215#endif
216 [[nodiscard]] auto All()
217 {
218 return RunFinisher([this] { return this->template AllImpl<Field>(); });
219 }
220
221 /// @brief Executes a SELECT query and returns all records found for the specified field,
222 /// only having the specified fields queried and populated.
223 ///
224 /// @tparam ReferencedFields The fields to select from the record, in the form of &Record::FieldName.
225 ///
226 /// @returns A vector of records with the given fields populated.
227 ///
228 /// @code
229 /// auto dm = DataMapper {};
230 /// auto const ages = dm.Query<Person>()
231 /// .OrderBy(FieldNameOf<&Person::age>, SqlResultOrdering::ASCENDING)
232 /// .All<&Person::name, &Person::age>();
233 /// @endcode
234 template <auto... ReferencedFields>
235 requires(sizeof...(ReferencedFields) >= 2)
236 [[nodiscard]] auto All()
237 {
238 return RunFinisher([this] { return this->template AllImpl<ReferencedFields...>(); });
239 }
240
241 /// Executes a SELECT query for the first record found and returns it.
242 [[nodiscard]] auto First()
243 {
244 return RunFinisher([this] { return FirstImpl(); });
245 }
246
247 /// @brief Executes the query to get a single scalar value from the first record found.
248 ///
249 /// @tparam Field The field to select from the record, in the form of &Record::FieldName.
250 ///
251 /// @returns an optional value of the type of the field, or an empty optional if no record was found.
252 template <auto Field>
253#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
254 requires(is_aggregate_type(parent_of(Field)))
255#else
256 requires std::is_member_object_pointer_v<decltype(Field)>
257#endif
258 [[nodiscard]] auto First()
259 {
260 return RunFinisher([this] { return this->template FirstImpl<Field>(); });
261 }
262
263 /// @brief Executes a SELECT query for the first record found and returns it with only the specified fields populated.
264 ///
265 /// @tparam ReferencedFields The fields to select from the record, in the form of &Record::FieldName.
266 ///
267 /// @returns an optional record with only the specified fields populated, or an empty optional if no record was found.
268 template <auto... ReferencedFields>
269 requires(sizeof...(ReferencedFields) >= 2)
270 [[nodiscard]] auto First()
271 {
272 return RunFinisher([this] { return this->template FirstImpl<ReferencedFields...>(); });
273 }
274
275 /// Executes a SELECT query for the first n records found and returns them.
276 [[nodiscard]] auto First(size_t n)
277 {
278 return RunFinisher([this, n] { return FirstImpl(n); });
279 }
280
281 /// Executes a SELECT query for the first n records with only the specified fields populated.
282 template <auto... ReferencedFields>
283 [[nodiscard]] auto First(size_t n)
284 {
285 return RunFinisher([this, n] { return this->template FirstImpl<ReferencedFields...>(n); });
286 }
287
288 /// Executes a SELECT query for a range of records and returns them.
289 [[nodiscard]] auto Range(size_t offset, size_t limit)
290 {
291 return RunFinisher([this, offset, limit] { return RangeImpl(offset, limit); });
292 }
293
294 /// Executes a SELECT query for a range of records with only the specified fields populated.
295 template <auto... ReferencedFields>
296 [[nodiscard]] auto Range(size_t offset, size_t limit)
297 {
298 return RunFinisher([this, offset, limit] { return this->template RangeImpl<ReferencedFields...>(offset, limit); });
299 }
300
301 private:
302 /// Dispatches a finisher according to the builder's execution mode.
303 ///
304 /// In @c Synchronous mode the finisher is invoked directly and its result returned. In
305 /// @c Asynchronous mode it is offloaded to the connection's async backend and an
306 /// @c Async::Task wrapping its result is returned instead.
307 ///
308 /// @param finisher A nullary callable running one of the synchronous @c *Impl methods.
309 /// @return The finisher's result (Synchronous) or an @c Async::Task of it (Asynchronous).
310 ///
311 /// @note Defined out-of-line in DataMapper.hpp, where @c DataMapper is a complete type (the
312 /// async branch dereferences it via @c _dm.Connection().AsyncBackend()).
313 template <typename Finisher>
314 auto RunFinisher(Finisher finisher);
315
316 // Synchronous implementations shared by both execution modes. The public finishers above
317 // forward to these; the SQL building and result mapping live here exactly once.
318
319 /// Runs every loader registered through `With<>()` over the materialized result set.
320 ///
321 /// @param records The records to resolve the requested relations for.
322 void RunRelationPreloaders(std::span<Record> records);
323
324 [[nodiscard]] bool ExistImpl();
325 [[nodiscard]] size_t CountImpl();
326 [[nodiscard]] std::vector<Record> AllImpl();
327 void DeleteImpl();
328
329 template <auto Field>
330#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
331 requires(is_aggregate_type(parent_of(Field)))
332#else
333 requires std::is_member_object_pointer_v<decltype(Field)>
334#endif
335 [[nodiscard]] auto AllImpl() -> std::vector<ReferencedFieldTypeOf<Field>>;
336
337 template <auto... ReferencedFields>
338 requires(sizeof...(ReferencedFields) >= 2)
339 [[nodiscard]] auto AllImpl() -> std::vector<Record>;
340
341 [[nodiscard]] std::optional<Record> FirstImpl();
342
343 template <auto Field>
344#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
345 requires(is_aggregate_type(parent_of(Field)))
346#else
347 requires std::is_member_object_pointer_v<decltype(Field)>
348#endif
349 [[nodiscard]] auto FirstImpl() -> std::optional<ReferencedFieldTypeOf<Field>>;
350
351 template <auto... ReferencedFields>
352 requires(sizeof...(ReferencedFields) >= 2)
353 [[nodiscard]] auto FirstImpl() -> std::optional<Record>;
354
355 [[nodiscard]] std::vector<Record> FirstImpl(size_t n);
356
357 template <auto... ReferencedFields>
358 [[nodiscard]] std::vector<Record> FirstImpl(size_t n);
359
360 [[nodiscard]] std::vector<Record> RangeImpl(size_t offset, size_t limit);
361
362 template <auto... ReferencedFields>
363 [[nodiscard]] std::vector<Record> RangeImpl(size_t offset, size_t limit);
364};
365
366/// @brief Represents a query builder that retrieves all fields of a record.
367///
368/// @ingroup DataMapper
369///
370/// @tparam Execution Whether the finisher methods execute synchronously or asynchronously.
371/// @c DataMapper::Query selects @c Synchronous, @c DataMapper::QueryAsync selects
372/// @c Asynchronous; the rest of the fluent builder is identical for both.
373template <typename Record,
374 DataMapperOptions QueryOptions,
375 SqlQueryExecutionMode Execution = SqlQueryExecutionMode::Synchronous>
376class [[nodiscard]] SqlAllFieldsQueryBuilder final:
377 public SqlCoreDataMapperQueryBuilder<Record, SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>, QueryOptions>
378{
379 private:
380 friend class DataMapper;
381 friend class SqlCoreDataMapperQueryBuilder<Record,
382 SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>,
383 QueryOptions>;
384
385 /// The execution mode (synchronous/asynchronous) read by the CRTP base to dispatch finishers.
386 static constexpr SqlQueryExecutionMode QueryExecution = Execution;
387
388 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(DataMapper& dm, std::string fields) noexcept:
390 dm, std::move(fields)
391 }
392 {
393 }
394
395 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<Record>* records);
396 static void ReadResult(SqlServerType sqlServerType, SqlResultCursor reader, std::optional<Record>* optionalRecord);
397};
398
399/// @brief Specialization of SqlAllFieldsQueryBuilder for the case when we return std::tuple
400/// of two records
401///
402/// @ingroup DataMapper
403/// @todo deprecate this in favor of a more generic tuple support
404template <typename FirstRecord, typename SecondRecord, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
405class [[nodiscard]] SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution> final:
407 std::tuple<FirstRecord, SecondRecord>,
408 SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution>,
409 QueryOptions>
410{
411 private:
412 using RecordType = std::tuple<FirstRecord, SecondRecord>;
413 friend class DataMapper;
414 friend class SqlCoreDataMapperQueryBuilder<RecordType,
415 SqlAllFieldsQueryBuilder<RecordType, QueryOptions, Execution>,
416 QueryOptions>;
417
418 /// The execution mode (synchronous/asynchronous) read by the CRTP base to dispatch finishers.
419 static constexpr SqlQueryExecutionMode QueryExecution = Execution;
420
421 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(DataMapper& dm, std::string fields) noexcept:
424 QueryOptions> { dm, std::move(fields) }
425 {
426 }
427
428 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records);
429};
430
431} // 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:154