Lightweight 0.20260617.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
15namespace Lightweight
16{
17
18class DataMapper;
19
20/// Structural type for options for DataMapper queries.
21/// This allows to configure behavior of the queries at compile time
22/// when using query builder directly from the DataMapper
24{
25 /// Whether to automatically load relations when querying records.
26 bool loadRelations { true };
27};
28
29/// Selects whether a query builder's finisher methods execute synchronously or asynchronously.
30///
31/// In @c Synchronous mode a finisher (e.g. @c All(), @c First(n)) runs immediately and returns its
32/// plain result. In @c Asynchronous mode the very same finisher offloads its work to the connection's
33/// async backend and returns a @c Async::Task of that result instead, to be @c co_await -ed.
34enum class SqlQueryExecutionMode : std::uint8_t
35{
36 /// The finisher runs on the calling thread and returns its result directly.
37 Synchronous,
38 /// The finisher returns an @c Async::Task that offloads the work and resumes the awaiting coroutine.
39 Asynchronous,
40};
41
42/// Main API for mapping records to C++ from the database using high level C++ syntax.
43///
44/// @ingroup DataMapper
45template <typename Record, typename Derived, DataMapperOptions QueryOptions = {}>
46class [[nodiscard]] SqlCoreDataMapperQueryBuilder: public SqlBasicSelectQueryBuilder<Derived>
47{
48 private:
49 DataMapper& _dm;
50 SqlQueryFormatter const& _formatter;
51
52 std::string _fields;
53 std::vector<SqlVariant> _boundInputs;
54
55 friend class SqlWhereClauseBuilder<Derived>;
56
57 LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SearchCondition() noexcept
58 {
59 return this->_query.searchCondition;
60 }
61
62 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE SqlQueryFormatter const& Formatter() const noexcept
63 {
64 return _formatter;
65 }
66
67 protected:
68 /// Constructs a query builder with the given data mapper and field list.
69 LIGHTWEIGHT_FORCE_INLINE explicit SqlCoreDataMapperQueryBuilder(DataMapper& dm, std::string fields) noexcept;
70
71 public:
72 // The public finisher methods below are thin dispatchers: each forwards to its synchronous
73 // implementation (the *Impl members) through RunFinisher(), which either calls it directly
74 // (Synchronous mode) or offloads it to the connection's async backend and returns an
75 // Async::Task (Asynchronous mode). The execution mode is carried by the Derived type
76 // (Derived::QueryExecution), so the same fluent builder serves both Query() and QueryAsync().
77
78 /// Executes a SELECT 1 ... query and returns true if a record exists
79 /// We do not provide db specific syntax to check this but reuse the First() implementation
80 [[nodiscard]] auto Exist()
81 {
82 return RunFinisher([this] { return ExistImpl(); });
83 }
84
85 /// Executes a SELECT COUNT query and returns the number of records found.
86 [[nodiscard]] auto Count()
87 {
88 return RunFinisher([this] { return CountImpl(); });
89 }
90
91 /// Executes a SELECT query and returns all records found.
92 [[nodiscard]] auto All()
93 {
94 return RunFinisher([this] { return AllImpl(); });
95 }
96
97 /// Executes a DELETE query.
98 [[nodiscard]] auto Delete()
99 {
100 return RunFinisher([this] { return DeleteImpl(); });
101 }
102
103 /// @brief Executes a SELECT query and returns all records found for the specified field.
104 ///
105 /// @tparam Field The field to select from the record, in the form of &Record::FieldName.
106 ///
107 /// @returns A vector of values of the type of the specified field.
108 ///
109 /// @code
110 /// auto dm = DataMapper {};
111 /// auto const ages = dm.Query<Person>()
112 /// .OrderBy(FieldNameOf<&Person::age>, SqlResultOrdering::ASCENDING)
113 /// .All<&Person::age>();
114 /// @endcode
115 template <auto Field>
116#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
117 requires(is_aggregate_type(parent_of(Field)))
118#else
119 requires std::is_member_object_pointer_v<decltype(Field)>
120#endif
121 [[nodiscard]] auto All()
122 {
123 return RunFinisher([this] { return this->template AllImpl<Field>(); });
124 }
125
126 /// @brief Executes a SELECT query and returns all records found for the specified field,
127 /// only having the specified fields queried and populated.
128 ///
129 /// @tparam ReferencedFields The fields to select from the record, in the form of &Record::FieldName.
130 ///
131 /// @returns A vector of records with the given fields populated.
132 ///
133 /// @code
134 /// auto dm = DataMapper {};
135 /// auto const ages = dm.Query<Person>()
136 /// .OrderBy(FieldNameOf<&Person::age>, SqlResultOrdering::ASCENDING)
137 /// .All<&Person::name, &Person::age>();
138 /// @endcode
139 template <auto... ReferencedFields>
140 requires(sizeof...(ReferencedFields) >= 2)
141 [[nodiscard]] auto All()
142 {
143 return RunFinisher([this] { return this->template AllImpl<ReferencedFields...>(); });
144 }
145
146 /// Executes a SELECT query for the first record found and returns it.
147 [[nodiscard]] auto First()
148 {
149 return RunFinisher([this] { return FirstImpl(); });
150 }
151
152 /// @brief Executes the query to get a single scalar value from the first record found.
153 ///
154 /// @tparam Field The field to select from the record, in the form of &Record::FieldName.
155 ///
156 /// @returns an optional value of the type of the field, or an empty optional if no record was found.
157 template <auto Field>
158#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
159 requires(is_aggregate_type(parent_of(Field)))
160#else
161 requires std::is_member_object_pointer_v<decltype(Field)>
162#endif
163 [[nodiscard]] auto First()
164 {
165 return RunFinisher([this] { return this->template FirstImpl<Field>(); });
166 }
167
168 /// @brief Executes a SELECT query for the first record found and returns it with only the specified fields populated.
169 ///
170 /// @tparam ReferencedFields The fields to select from the record, in the form of &Record::FieldName.
171 ///
172 /// @returns an optional record with only the specified fields populated, or an empty optional if no record was found.
173 template <auto... ReferencedFields>
174 requires(sizeof...(ReferencedFields) >= 2)
175 [[nodiscard]] auto First()
176 {
177 return RunFinisher([this] { return this->template FirstImpl<ReferencedFields...>(); });
178 }
179
180 /// Executes a SELECT query for the first n records found and returns them.
181 [[nodiscard]] auto First(size_t n)
182 {
183 return RunFinisher([this, n] { return FirstImpl(n); });
184 }
185
186 /// Executes a SELECT query for the first n records with only the specified fields populated.
187 template <auto... ReferencedFields>
188 [[nodiscard]] auto First(size_t n)
189 {
190 return RunFinisher([this, n] { return this->template FirstImpl<ReferencedFields...>(n); });
191 }
192
193 /// Executes a SELECT query for a range of records and returns them.
194 [[nodiscard]] auto Range(size_t offset, size_t limit)
195 {
196 return RunFinisher([this, offset, limit] { return RangeImpl(offset, limit); });
197 }
198
199 /// Executes a SELECT query for a range of records with only the specified fields populated.
200 template <auto... ReferencedFields>
201 [[nodiscard]] auto Range(size_t offset, size_t limit)
202 {
203 return RunFinisher([this, offset, limit] { return this->template RangeImpl<ReferencedFields...>(offset, limit); });
204 }
205
206 private:
207 /// Dispatches a finisher according to the builder's execution mode.
208 ///
209 /// In @c Synchronous mode the finisher is invoked directly and its result returned. In
210 /// @c Asynchronous mode it is offloaded to the connection's async backend and an
211 /// @c Async::Task wrapping its result is returned instead.
212 ///
213 /// @param finisher A nullary callable running one of the synchronous @c *Impl methods.
214 /// @return The finisher's result (Synchronous) or an @c Async::Task of it (Asynchronous).
215 ///
216 /// @note Defined out-of-line in DataMapper.hpp, where @c DataMapper is a complete type (the
217 /// async branch dereferences it via @c _dm.Connection().AsyncBackend()).
218 template <typename Finisher>
219 auto RunFinisher(Finisher finisher);
220
221 // Synchronous implementations shared by both execution modes. The public finishers above
222 // forward to these; the SQL building and result mapping live here exactly once.
223
224 [[nodiscard]] bool ExistImpl();
225 [[nodiscard]] size_t CountImpl();
226 [[nodiscard]] std::vector<Record> AllImpl();
227 void DeleteImpl();
228
229 template <auto Field>
230#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
231 requires(is_aggregate_type(parent_of(Field)))
232#else
233 requires std::is_member_object_pointer_v<decltype(Field)>
234#endif
235 [[nodiscard]] auto AllImpl() -> std::vector<ReferencedFieldTypeOf<Field>>;
236
237 template <auto... ReferencedFields>
238 requires(sizeof...(ReferencedFields) >= 2)
239 [[nodiscard]] auto AllImpl() -> std::vector<Record>;
240
241 [[nodiscard]] std::optional<Record> FirstImpl();
242
243 template <auto Field>
244#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
245 requires(is_aggregate_type(parent_of(Field)))
246#else
247 requires std::is_member_object_pointer_v<decltype(Field)>
248#endif
249 [[nodiscard]] auto FirstImpl() -> std::optional<ReferencedFieldTypeOf<Field>>;
250
251 template <auto... ReferencedFields>
252 requires(sizeof...(ReferencedFields) >= 2)
253 [[nodiscard]] auto FirstImpl() -> std::optional<Record>;
254
255 [[nodiscard]] std::vector<Record> FirstImpl(size_t n);
256
257 template <auto... ReferencedFields>
258 [[nodiscard]] std::vector<Record> FirstImpl(size_t n);
259
260 [[nodiscard]] std::vector<Record> RangeImpl(size_t offset, size_t limit);
261
262 template <auto... ReferencedFields>
263 [[nodiscard]] std::vector<Record> RangeImpl(size_t offset, size_t limit);
264};
265
266/// @brief Represents a query builder that retrieves all fields of a record.
267///
268/// @ingroup DataMapper
269///
270/// @tparam Execution Whether the finisher methods execute synchronously or asynchronously.
271/// @c DataMapper::Query selects @c Synchronous, @c DataMapper::QueryAsync selects
272/// @c Asynchronous; the rest of the fluent builder is identical for both.
273template <typename Record,
274 DataMapperOptions QueryOptions,
275 SqlQueryExecutionMode Execution = SqlQueryExecutionMode::Synchronous>
276class [[nodiscard]] SqlAllFieldsQueryBuilder final:
277 public SqlCoreDataMapperQueryBuilder<Record, SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>, QueryOptions>
278{
279 private:
280 friend class DataMapper;
281 friend class SqlCoreDataMapperQueryBuilder<Record,
282 SqlAllFieldsQueryBuilder<Record, QueryOptions, Execution>,
283 QueryOptions>;
284
285 /// The execution mode (synchronous/asynchronous) read by the CRTP base to dispatch finishers.
286 static constexpr SqlQueryExecutionMode QueryExecution = Execution;
287
288 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(DataMapper& dm, std::string fields) noexcept:
290 dm, std::move(fields)
291 }
292 {
293 }
294
295 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<Record>* records);
296 static void ReadResult(SqlServerType sqlServerType, SqlResultCursor reader, std::optional<Record>* optionalRecord);
297};
298
299/// @brief Specialization of SqlAllFieldsQueryBuilder for the case when we return std::tuple
300/// of two records
301///
302/// @ingroup DataMapper
303/// @todo deprecate this in favor of a more generic tuple support
304template <typename FirstRecord, typename SecondRecord, DataMapperOptions QueryOptions, SqlQueryExecutionMode Execution>
305class [[nodiscard]] SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution> final:
307 std::tuple<FirstRecord, SecondRecord>,
308 SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>, QueryOptions, Execution>,
309 QueryOptions>
310{
311 private:
312 using RecordType = std::tuple<FirstRecord, SecondRecord>;
313 friend class DataMapper;
314 friend class SqlCoreDataMapperQueryBuilder<RecordType,
315 SqlAllFieldsQueryBuilder<RecordType, QueryOptions, Execution>,
316 QueryOptions>;
317
318 /// The execution mode (synchronous/asynchronous) read by the CRTP base to dispatch finishers.
319 static constexpr SqlQueryExecutionMode QueryExecution = Execution;
320
321 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(DataMapper& dm, std::string fields) noexcept:
324 QueryOptions> { dm, std::move(fields) }
325 {
326 }
327
328 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records);
329};
330
331} // 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 Count()
Executes a SELECT COUNT query and returns the number of records found.
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.
API for reading an SQL query result set.
bool loadRelations
Whether to automatically load relations when querying records.
Represents a single column in a table.
Definition Field.hpp:84