Lightweight 0.20250904.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
13namespace Lightweight
14{
15
16namespace detail
17{
18 template <typename FieldType>
19 constexpr bool CanSafelyBindOutputColumn(SqlServerType sqlServerType) noexcept
20 {
21 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
22 return true;
23
24 // Test if we have some columns that might not be sufficient to store the result (e.g. string truncation),
25 // then don't call BindOutputColumn but SQLFetch to get the result, because
26 // regrowing previously bound columns is not supported in MS-SQL's ODBC driver, so it seems.
27 bool result = true;
28 if constexpr (IsField<FieldType>)
29 {
30 if constexpr (detail::OneOf<typename FieldType::ValueType,
31 std::string,
32 std::wstring,
33 std::u16string,
34 std::u32string,
35 SqlBinary>
36 || IsSqlDynamicString<typename FieldType::ValueType>
37 || IsSqlDynamicBinary<typename FieldType::ValueType>)
38 {
39 // Known types that MAY require growing due to truncation.
40 result = false;
41 }
42 }
43 return result;
44 }
45
46 template <DataMapperRecord Record>
47 constexpr bool CanSafelyBindOutputColumns(SqlServerType sqlServerType) noexcept
48 {
49 if (sqlServerType != SqlServerType::MICROSOFT_SQL)
50 return true;
51
52 bool result = true;
53 Reflection::EnumerateMembers<Record>([&result]<size_t I, typename Field>() {
54 if constexpr (IsField<Field>)
55 {
56 if constexpr (detail::OneOf<typename Field::ValueType,
57 std::string,
58 std::wstring,
59 std::u16string,
60 std::u32string,
61 SqlBinary>
62 || IsSqlDynamicString<typename Field::ValueType>
63 || IsSqlDynamicBinary<typename Field::ValueType>)
64 {
65 // Known types that MAY require growing due to truncation.
66 result = false;
67 }
68 }
69 });
70 return result;
71 }
72
73 template <typename Record>
74 void BindAllOutputColumnsWithOffset(SqlResultCursor& reader, Record& record, SQLSMALLINT startOffset)
75 {
76 Reflection::EnumerateMembers(record,
77 [reader = &reader, i = startOffset]<size_t I, typename Field>(Field& field) mutable {
78 if constexpr (IsField<Field>)
79 {
80 reader->BindOutputColumn(i++, &field.MutableValue());
81 }
82 else if constexpr (IsBelongsTo<Field>)
83 {
84 reader->BindOutputColumn(i++, &field.MutableValue());
85 }
86 else if constexpr (SqlOutputColumnBinder<Field>)
87 {
88 reader->BindOutputColumn(i++, &field);
89 }
90 });
91 }
92
93 template <typename Record>
94 void BindAllOutputColumns(SqlResultCursor& reader, Record& record)
95 {
96 BindAllOutputColumnsWithOffset(reader, record, 1);
97 }
98
99 // when we iterate over all columns using element mask
100 // indexes of the mask corresponds to the indexe of the field
101 // inside the structure, not inside the SQL result set
102 template <typename ElementMask, typename Record>
103 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
104 {
105 Reflection::EnumerateMembers<ElementMask>(
106 record, [reader = &reader, &indexFromQuery]<size_t I, typename Field>(Field& field) mutable {
107 ++indexFromQuery;
108 if constexpr (IsField<Field>)
109 {
110 if constexpr (Field::IsOptional)
111 field.MutableValue() =
112 reader->GetNullableColumn<typename Field::ValueType::value_type>(indexFromQuery);
113 else
114 field.MutableValue() = reader->GetColumn<typename Field::ValueType>(indexFromQuery);
115 }
116 else if constexpr (SqlGetColumnNativeType<Field>)
117 {
118 if constexpr (IsOptionalBelongsTo<Field>)
119 field = reader->GetNullableColumn<typename Field::BaseType>(indexFromQuery);
120 else
121 field = reader->GetColumn<Field>(indexFromQuery);
122 }
123 });
124 }
125
126 template <typename Record>
127 void GetAllColumns(SqlResultCursor& reader, Record& record, SQLUSMALLINT indexFromQuery = 0)
128 {
129 return GetAllColumns<std::make_integer_sequence<size_t, Reflection::CountMembers<Record>>, Record>(
130 reader, record, indexFromQuery);
131 }
132
133 template <typename FirstRecord, typename SecondRecord>
134 // TODO we need to remove this at some points and provide generic bindings for tuples
135 void GetAllColumns(SqlResultCursor& reader, std::tuple<FirstRecord, SecondRecord>& record)
136 {
137 auto& [firstRecord, secondRecord] = record;
138
139 Reflection::EnumerateMembers(firstRecord, [reader = &reader]<size_t I, typename Field>(Field& field) mutable {
140 if constexpr (IsField<Field>)
141 {
142 if constexpr (Field::IsOptional)
143 field.MutableValue() = reader->GetNullableColumn<typename Field::ValueType::value_type>(I + 1);
144 else
145 field.MutableValue() = reader->GetColumn<typename Field::ValueType>(I + 1);
146 }
147 else if constexpr (SqlGetColumnNativeType<Field>)
148 {
149 if constexpr (Field::IsOptional)
150 field = reader->GetNullableColumn<typename Field::BaseType>(I + 1);
151 else
152 field = reader->GetColumn<Field>(I + 1);
153 }
154 });
155
156 Reflection::EnumerateMembers(secondRecord, [reader = &reader]<size_t I, typename Field>(Field& field) mutable {
157 if constexpr (IsField<Field>)
158 {
159 if constexpr (Field::IsOptional)
160 field.MutableValue() = reader->GetNullableColumn<typename Field::ValueType::value_type>(
161 Reflection::CountMembers<FirstRecord> + I + 1);
162 else
163 field.MutableValue() =
164 reader->GetColumn<typename Field::ValueType>(Reflection::CountMembers<FirstRecord> + I + 1);
165 }
166 else if constexpr (SqlGetColumnNativeType<Field>)
167 {
168 if constexpr (Field::IsOptional)
169 field =
170 reader->GetNullableColumn<typename Field::BaseType>(Reflection::CountMembers<FirstRecord> + I + 1);
171 else
172 field = reader->GetColumn<Field>(Reflection::CountMembers<FirstRecord> + I + 1);
173 }
174 });
175 }
176
177 template <typename Record>
178 bool ReadSingleResult(SqlServerType sqlServerType, SqlResultCursor& reader, Record& record)
179 {
180 auto const outputColumnsBound = CanSafelyBindOutputColumns<Record>(sqlServerType);
181
182 if (outputColumnsBound)
183 BindAllOutputColumns(reader, record);
184
185 if (!reader.FetchRow())
186 return false;
187
188 if (!outputColumnsBound)
189 GetAllColumns(reader, record);
190
191 return true;
192 }
193} // namespace detail
194
195/// Main API for mapping records to C++ from the database using high level C++ syntax.
196///
197/// @ingroup DataMapper
198template <typename Record, typename Derived>
199class [[nodiscard]] SqlCoreDataMapperQueryBuilder: public SqlBasicSelectQueryBuilder<Derived>
200{
201 private:
202 SqlStatement& _stmt;
203 SqlQueryFormatter const& _formatter;
204
205 std::string _fields;
206
207 friend class SqlWhereClauseBuilder<Derived>;
208
209 LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SearchCondition() noexcept
210 {
211 return this->_query.searchCondition;
212 }
213
214 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE SqlQueryFormatter const& Formatter() const noexcept
215 {
216 return _formatter;
217 }
218
219 protected:
220 LIGHTWEIGHT_FORCE_INLINE explicit SqlCoreDataMapperQueryBuilder(SqlStatement& stmt, std::string fields) noexcept:
221 _stmt { stmt },
222 _formatter { stmt.Connection().QueryFormatter() },
223 _fields { std::move(fields) }
224 {
225 }
226
227 public:
228 /// Executes a SELECT COUNT query and returns the number of records found.
229 [[nodiscard]] size_t Count()
230 {
231 _stmt.ExecuteDirect(_formatter.SelectCount(this->_query.distinct,
232 RecordTableName<Record>,
233 this->_query.searchCondition.tableAlias,
234 this->_query.searchCondition.tableJoins,
235 this->_query.searchCondition.condition));
236 auto reader = _stmt.GetResultCursor();
237 if (reader.FetchRow())
238 return reader.GetColumn<size_t>(1);
239 return 0;
240 }
241
242 /// Executes a SELECT query and returns all records found.
243 [[nodiscard]] std::vector<Record> All()
244 {
245 auto records = std::vector<Record> {};
246 _stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
247 _fields,
248 RecordTableName<Record>,
249 this->_query.searchCondition.tableAlias,
250 this->_query.searchCondition.tableJoins,
251 this->_query.searchCondition.condition,
252 this->_query.orderBy,
253 this->_query.groupBy));
254 Derived::ReadResults(_stmt.Connection().ServerType(), _stmt.GetResultCursor(), &records);
255 return records;
256 }
257
258 /// @brief Executes a SELECT query and returns all records found for the specified field.
259 ///
260 /// @tparam Field The field to select from the record, in the form of &Record::FieldName.
261 ///
262 /// @returns A vector of values of the type of the specified field.
263 ///
264 /// @code
265 /// auto dm = DataMapper {};
266 /// auto const ages = dm.Query<Person>()
267 /// .OrderBy(FieldNameOf<&Person::age>, SqlResultOrdering::ASCENDING)
268 /// .All<&Person::age>();
269 /// @endcode
270 template <auto Field>
271 [[nodiscard]] auto All() -> std::vector<ReferencedFieldTypeOf<Field>>
272 {
273 using value_type = ReferencedFieldTypeOf<Field>;
274 auto result = std::vector<value_type> {};
275
276 _stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
277 FullyQualifiedNamesOf<Field>.string_view(),
278 RecordTableName<Record>,
279 this->_query.searchCondition.tableAlias,
280 this->_query.searchCondition.tableJoins,
281 this->_query.searchCondition.condition,
282 this->_query.orderBy,
283 this->_query.groupBy));
284 SqlResultCursor reader = _stmt.GetResultCursor();
285 auto const outputColumnsBound = detail::CanSafelyBindOutputColumn<value_type>(_stmt.Connection().ServerType());
286 while (true)
287 {
288 auto& value = result.emplace_back();
289 if (outputColumnsBound)
290 reader.BindOutputColumn(1, &value);
291
292 if (!reader.FetchRow())
293 {
294 result.pop_back();
295 break;
296 }
297
298 if (!outputColumnsBound)
299 value = reader.GetColumn<value_type>(1);
300 }
301
302 return result;
303 }
304
305 /// @brief Executes a SELECT query and returns all records found for the specified field,
306 /// only having the specified fields queried and populated.
307 ///
308 /// @tparam ReferencedFields The fields to select from the record, in the form of &Record::FieldName.
309 ///
310 /// @returns A vector of records with the given fields populated.
311 ///
312 /// @code
313 /// auto dm = DataMapper {};
314 /// auto const ages = dm.Query<Person>()
315 /// .OrderBy(FieldNameOf<&Person::age>, SqlResultOrdering::ASCENDING)
316 /// .All<&Person::name, &Person::age>();
317 /// @endcode
318 template <auto... ReferencedFields>
319 requires(sizeof...(ReferencedFields) >= 2)
320 [[nodiscard]] auto All() -> std::vector<Record>
321 {
322 auto records = std::vector<Record> {};
323
324 _stmt.ExecuteDirect(_formatter.SelectAll(this->_query.distinct,
325 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
326 RecordTableName<Record>,
327 this->_query.searchCondition.tableAlias,
328 this->_query.searchCondition.tableJoins,
329 this->_query.searchCondition.condition,
330 this->_query.orderBy,
331 this->_query.groupBy));
332
333 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
334 SqlResultCursor reader = _stmt.GetResultCursor();
335 while (true)
336 {
337 auto& record = records.emplace_back();
338 if (outputColumnsBound)
339#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
340 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
341#else
342 reader.BindOutputColumns(&(record.*ReferencedFields)...);
343#endif
344 if (!reader.FetchRow())
345 {
346 records.pop_back();
347 break;
348 }
349 if (!outputColumnsBound)
350 {
351 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
352 detail::GetAllColumns<ElementMask>(reader, record);
353 }
354 }
355
356 return records;
357 }
358
359 /// Executes a SELECT query for the first record found and returns it.
360 [[nodiscard]] std::optional<Record> First()
361 {
362 std::optional<Record> record {};
363 _stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
364 _fields,
365 RecordTableName<Record>,
366 this->_query.searchCondition.tableAlias,
367 this->_query.searchCondition.tableJoins,
368 this->_query.searchCondition.condition,
369 this->_query.orderBy,
370 1));
371 Derived::ReadResult(_stmt.Connection().ServerType(), _stmt.GetResultCursor(), &record);
372 return record;
373 }
374
375 /// @brief Executes the query to get a single scalar value from the first record found.
376 ///
377 /// @tparam Field The field to select from the record, in the form of &Record::FieldName.
378 ///
379 /// @returns an optional value of the type of the field, or an empty optional if no record was found.
380 template <auto Field>
381 [[nodiscard]] auto First() -> std::optional<ReferencedFieldTypeOf<Field>>
382 {
383 auto constexpr count = 1;
384 _stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
385 FullyQualifiedNamesOf<Field>.string_view(),
386 RecordTableName<Record>,
387 this->_query.searchCondition.tableAlias,
388 this->_query.searchCondition.tableJoins,
389 this->_query.searchCondition.condition,
390 this->_query.orderBy,
391 count));
392 if (SqlResultCursor reader = _stmt.GetResultCursor(); reader.FetchRow())
393 return reader.template GetColumn<ReferencedFieldTypeOf<Field>>(1);
394 return std::nullopt;
395 }
396
397 /// @brief Executes a SELECT query for the first record found and returns it with only the specified fields populated.
398 ///
399 /// @tparam ReferencedFields The fields to select from the record, in the form of &Record::FieldName.
400 ///
401 /// @returns an optional record with only the specified fields populated, or an empty optional if no record was found.
402 template <auto... ReferencedFields>
403 requires(sizeof...(ReferencedFields) >= 2)
404 [[nodiscard]] auto First() -> std::optional<Record>
405 {
406 auto optionalRecord = std::optional<Record> {};
407
408 _stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
409 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
410 RecordTableName<Record>,
411 this->_query.searchCondition.tableAlias,
412 this->_query.searchCondition.tableJoins,
413 this->_query.searchCondition.condition,
414 this->_query.orderBy,
415 1));
416
417 auto& record = optionalRecord.emplace();
418 SqlResultCursor reader = _stmt.GetResultCursor();
419 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
420 if (outputColumnsBound)
421#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
422 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
423#else
424 reader.BindOutputColumns(&(record.*ReferencedFields)...);
425#endif
426 if (!reader.FetchRow())
427 return std::nullopt;
428 if (!outputColumnsBound)
429 {
430 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
431 detail::GetAllColumns<ElementMask>(reader, record);
432 }
433
434 return optionalRecord;
435 }
436
437 /// Executes a SELECT query for the first n records found and returns them.
438 [[nodiscard]] std::vector<Record> First(size_t n)
439 {
440 auto records = std::vector<Record> {};
441 records.reserve(n);
442 _stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
443 _fields,
444 RecordTableName<Record>,
445 this->_query.searchCondition.tableAlias,
446 this->_query.searchCondition.tableJoins,
447 this->_query.searchCondition.condition,
448 this->_query.orderBy,
449 n));
450 Derived::ReadResults(_stmt.Connection().ServerType(), _stmt.GetResultCursor(), &records);
451 return records;
452 }
453
454 template <auto... ReferencedFields>
455 [[nodiscard]] std::vector<Record> First(size_t n)
456 {
457 auto records = std::vector<Record> {};
458 records.reserve(n);
459 _stmt.ExecuteDirect(_formatter.SelectFirst(this->_query.distinct,
460 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
461 RecordTableName<Record>,
462 this->_query.searchCondition.tableAlias,
463 this->_query.searchCondition.tableJoins,
464 this->_query.searchCondition.condition,
465 this->_query.orderBy,
466 n));
467
468 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
469 SqlResultCursor reader = _stmt.GetResultCursor();
470 while (true)
471 {
472 auto& record = records.emplace_back();
473 if (outputColumnsBound)
474#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
475 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
476#else
477 reader.BindOutputColumns(&(record.*ReferencedFields)...);
478#endif
479 if (!reader.FetchRow())
480 {
481 records.pop_back();
482 break;
483 }
484 if (!outputColumnsBound)
485 {
486 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
487 detail::GetAllColumns<ElementMask>(reader, record);
488 }
489 }
490
491 return records;
492 }
493
494 /// Executes a SELECT query for a range of records and returns them.
495 [[nodiscard]] std::vector<Record> Range(size_t offset, size_t limit)
496 {
497 auto records = std::vector<Record> {};
498 records.reserve(limit);
499 _stmt.ExecuteDirect(_formatter.SelectRange(
500 this->_query.distinct,
501 _fields,
502 RecordTableName<Record>,
503 this->_query.searchCondition.tableAlias,
504 this->_query.searchCondition.tableJoins,
505 this->_query.searchCondition.condition,
506 !this->_query.orderBy.empty()
507 ? this->_query.orderBy
508 : std::format(" ORDER BY \"{}\" ASC", FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
509 this->_query.groupBy,
510 offset,
511 limit));
512 Derived::ReadResults(_stmt.Connection().ServerType(), _stmt.GetResultCursor(), &records);
513 return records;
514 }
515
516 template <auto... ReferencedFields>
517 [[nodiscard]] std::vector<Record> Range(size_t offset, size_t limit)
518 {
519 auto records = std::vector<Record> {};
520 records.reserve(limit);
521 _stmt.ExecuteDirect(_formatter.SelectRange(
522 this->_query.distinct,
523 FullyQualifiedNamesOf<ReferencedFields...>.string_view(),
524 RecordTableName<Record>,
525 this->_query.searchCondition.tableAlias,
526 this->_query.searchCondition.tableJoins,
527 this->_query.searchCondition.condition,
528 !this->_query.orderBy.empty()
529 ? this->_query.orderBy
530 : std::format(" ORDER BY \"{}\" ASC", FieldNameAt<RecordPrimaryKeyIndex<Record>, Record>),
531 this->_query.groupBy,
532 offset,
533 limit));
534
535 auto const outputColumnsBound = detail::CanSafelyBindOutputColumns<Record>(_stmt.Connection().ServerType());
536 SqlResultCursor reader = _stmt.GetResultCursor();
537 while (true)
538 {
539 auto& record = records.emplace_back();
540 if (outputColumnsBound)
541#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
542 reader.BindOutputColumns(&(record.[:ReferencedFields:])...);
543#else
544 reader.BindOutputColumns(&(record.*ReferencedFields)...);
545#endif
546 if (!reader.FetchRow())
547 {
548 records.pop_back();
549 break;
550 }
551 if (!outputColumnsBound)
552 {
553 using ElementMask = std::integer_sequence<size_t, MemberIndexOf<ReferencedFields>...>;
554 detail::GetAllColumns<ElementMask>(reader, record);
555 }
556 }
557
558 return records;
559 }
560};
561
562/// @brief Represents a query builder that retrieves all fields of a record.
563///
564/// @ingroup DataMapper
565template <typename Record>
566class [[nodiscard]] SqlAllFieldsQueryBuilder final:
567 public SqlCoreDataMapperQueryBuilder<Record, SqlAllFieldsQueryBuilder<Record>>
568{
569 private:
570 friend class DataMapper;
571 friend class SqlCoreDataMapperQueryBuilder<Record, SqlAllFieldsQueryBuilder<Record>>;
572
573 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(SqlStatement& stmt, std::string fields) noexcept:
575 {
576 }
577
578 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<Record>* records)
579 {
580 while (true)
581 {
582 Record& record = records->emplace_back();
583 if (!detail::ReadSingleResult(sqlServerType, reader, record))
584 {
585 records->pop_back();
586 break;
587 }
588 }
589 }
590
591 static void ReadResult(SqlServerType sqlServerType, SqlResultCursor reader, std::optional<Record>* optionalRecord)
592 {
593 Record& record = optionalRecord->emplace();
594 if (!detail::ReadSingleResult(sqlServerType, reader, record))
595 optionalRecord->reset();
596 }
597};
598
599/// @brief Specialization of SqlAllFieldsQueryBuilder for the case when we return std::tuple
600/// of two records
601///
602/// @ingroup DataMapper
603template <typename FirstRecord, typename SecondRecord>
604class [[nodiscard]] SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>> final:
605 public SqlCoreDataMapperQueryBuilder<std::tuple<FirstRecord, SecondRecord>,
606 SqlAllFieldsQueryBuilder<std::tuple<FirstRecord, SecondRecord>>>
607{
608 private:
609 using RecordType = std::tuple<FirstRecord, SecondRecord>;
610 friend class DataMapper;
611 friend class SqlCoreDataMapperQueryBuilder<RecordType, SqlAllFieldsQueryBuilder<RecordType>>;
612
613 LIGHTWEIGHT_FORCE_INLINE explicit SqlAllFieldsQueryBuilder(SqlStatement& stmt, std::string fields) noexcept:
615 {
616 }
617
618 static void ReadResults(SqlServerType sqlServerType, SqlResultCursor reader, std::vector<RecordType>* records)
619 {
620 while (true)
621 {
622 auto& record = records->emplace_back();
623 auto& [firstRecord, secondRecord] = record;
624
625 using FirstRecordType = std::remove_cvref_t<decltype(firstRecord)>;
626 using SecondRecordType = std::remove_cvref_t<decltype(secondRecord)>;
627
628 auto const outputColumnsBoundFirst = detail::CanSafelyBindOutputColumns<FirstRecordType>(sqlServerType);
629 auto const outputColumnsBoundSecond = detail::CanSafelyBindOutputColumns<SecondRecordType>(sqlServerType);
630 auto const canSafelyBindAll = outputColumnsBoundFirst && outputColumnsBoundSecond;
631
632 if (canSafelyBindAll)
633 {
634 detail::BindAllOutputColumnsWithOffset(reader, firstRecord, 1);
635 detail::BindAllOutputColumnsWithOffset(reader, secondRecord, 1 + Reflection::CountMembers<FirstRecord>);
636 }
637
638 if (!reader.FetchRow())
639 {
640 records->pop_back();
641 break;
642 }
643
644 if (!canSafelyBindAll)
645 detail::GetAllColumns(reader, record);
646 }
647 }
648};
649
650} // 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.
SqlServerType ServerType() const noexcept
Retrieves the type of the server.
SqlQueryFormatter const & QueryFormatter() const noexcept
Retrieves a query formatter suitable for the SQL server being connected.
std::vector< Record > First(size_t n)
Executes a SELECT query for the first n records found and returns them.
std::optional< Record > First()
Executes a SELECT query for the first record found and returns it.
auto First() -> std::optional< Record >
Executes a SELECT query for the first record found and returns it with only the specified fields popu...
std::vector< Record > Range(size_t offset, size_t limit)
Executes a SELECT query for a range of records and returns them.
size_t Count()
Executes a SELECT COUNT query and returns the number of records found.
auto First() -> std::optional< ReferencedFieldTypeOf< Field > >
Executes the query to get a single scalar value from the first record found.
auto All() -> std::vector< ReferencedFieldTypeOf< Field > >
Executes a SELECT query and returns all records found for the specified field.
auto All() -> std::vector< Record >
Executes a SELECT query and returns all records found for the specified field, only having the specif...
std::vector< Record > All()
Executes a SELECT query and returns all records found.
API to format SQL queries for different SQL dialects.
virtual std::string SelectCount(bool distinct, std::string_view fromTable, std::string_view fromTableAlias, std::string_view tableJoins, std::string_view whereCondition) const =0
Constructs an SQL SELECT query retrieve the count of rows matching the given condition.
virtual std::string SelectAll(bool distinct, std::string_view fields, std::string_view fromTable, std::string_view fromTableAlias, std::string_view tableJoins, std::string_view whereCondition, std::string_view orderBy, std::string_view groupBy) const =0
Constructs an SQL SELECT query for all rows.
API for reading an SQL query result set.
LIGHTWEIGHT_FORCE_INLINE bool GetColumn(SQLUSMALLINT column, T *result) const
LIGHTWEIGHT_FORCE_INLINE void BindOutputColumns(Args *... args)
LIGHTWEIGHT_FORCE_INLINE bool FetchRow()
Fetches the next row of the result set.
High level API for (prepared) raw SQL statements.
LIGHTWEIGHT_API SqlConnection & Connection() noexcept
Retrieves the connection associated with this statement.
SqlResultCursor GetResultCursor() noexcept
Retrieves the result cursor for reading an SQL query result.
LIGHTWEIGHT_API void ExecuteDirect(std::string_view const &query, std::source_location location=std::source_location::current())
Executes the given query directly.
typename std::remove_cvref_t< decltype(std::declval< MemberClassType< decltype(Field)> >().*Field)>::ValueType ReferencedFieldTypeOf
Retrieves the type of a member field in a record.
Definition Field.hpp:362
constexpr std::string_view FieldNameAt
Returns the SQL field name of the given field index in the record.
Definition Utils.hpp:170
static constexpr auto IsOptional
Indicates if the field is optional, i.e., it can be NULL.
Definition Field.hpp:111